]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.9-20240208
authorWietse Z Venema <wietse@porcupine.org>
Thu, 8 Feb 2024 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Sat, 10 Feb 2024 02:29:50 +0000 (21:29 -0500)
44 files changed:
postfix/HISTORY
postfix/INSTALL
postfix/README_FILES/AAAREADME
postfix/README_FILES/DATABASE_README
postfix/README_FILES/INSTALL
postfix/README_FILES/MONGODB_README [new file with mode: 0644]
postfix/conf/dynamicmaps.cf
postfix/conf/postfix-files
postfix/html/DATABASE_README.html
postfix/html/INSTALL.html
postfix/html/MONGODB_README.html [new file with mode: 0644]
postfix/html/Makefile.in
postfix/html/index.html
postfix/html/makedefs.1.html
postfix/html/mongodb_table.5.html [new file with mode: 0644]
postfix/html/postfix-manuals.html
postfix/html/postfix.1.html
postfix/makedefs
postfix/man/Makefile.in
postfix/man/man1/makedefs.1
postfix/man/man1/postfix.1
postfix/man/man5/mongodb_table.5 [new file with mode: 0644]
postfix/mantools/postlink
postfix/proto/DATABASE_README.html
postfix/proto/INSTALL.html
postfix/proto/MONGODB_README.html [new file with mode: 0644]
postfix/proto/Makefile.in
postfix/proto/mongodb_table [new file with mode: 0644]
postfix/proto/stop
postfix/proto/stop.double-proto-html
postfix/proto/stop.spell-cc
postfix/proto/stop.spell-history
postfix/proto/stop.spell-proto-html
postfix/src/global/Makefile.in
postfix/src/global/dict_mongodb.c [new file with mode: 0644]
postfix/src/global/dict_mongodb.h [new file with mode: 0755]
postfix/src/global/mail_dict.c
postfix/src/global/mail_version.h
postfix/src/postconf/Makefile.in
postfix/src/postfix/postfix.c
postfix/src/postqueue/showq_json.c
postfix/src/util/Makefile.in
postfix/src/util/quote_for_json.c [new file with mode: 0644]
postfix/src/util/stringops.h

index a16d57d5c4b551ebd2dca2710e2c0f054851366c..fc0fa24c6bf3e717b5ad28157c901f367535c359 100644 (file)
@@ -27810,3 +27810,19 @@ Apologies for any names omitted.
        Documentation: the post-install(1) manpage now lists
        $config_directory/makedefs.out as one of the installed
        files. File: postfix-install.
+
+20240208
+
+       Refactored the JSON string quoting function, so that it can
+       be shared between the postqueue command and the MongoDB
+       client implementation. Files: util.quote_for_json.c,
+       util/stringops.h, postqueue/showq_json.c.
+
+       MongoDB client support, contributed by Hamid Maadani, based
+       on earlier code by Stephan Ferraro. Files: conf/dynamicmaps.cf,
+       conf/postfix-files, makedefs, mantools/postlink,
+       proto/DATABASE_README.html, proto/Makefile.in,
+       proto/MONGODB_README.html, proto/mongodb_table,
+       global/dict_mongodb.c, global/dict_mongodb.h, global/mail_dict.c,
+       global/Makefile.in, postconf/Makefile.in, proto/INSTALL.html,
+       postfix/postfix.c.
index 5939a995a61b2a095f4cdbd7f1fd2047fbff6347..90b1b2d8a2d5136abc8a1840b64781b2103c0aa1 100644 (file)
@@ -376,27 +376,29 @@ whistles. Support for third-party databases etc. must be configured when
 Postfix is compiled. The following documents describe how to build Postfix with
 support for optional features:
 
-     _____________________________________________________________
-    |Optional feature                 |Document     |Availability|
-    |__________________________________|_____________|____________|
-    |Berkeley DB database             |DB_README    |Postfix 1.0 |
-    |__________________________________|_____________|____________|
-    |LMDB database                    |LMDB_README  |Postfix 2.11|
-    |__________________________________|_____________|____________|
-    |LDAP database                    |LDAP_README  |Postfix 1.0 |
-    |__________________________________|_____________|____________|
-    |MySQL database                   |MYSQL_README |Postfix 1.0 |
-    |__________________________________|_____________|____________|
-    |Perl compatible regular expression|PCRE_README  |Postfix 1.0 |
-    |__________________________________|_____________|____________|
-    |PostgreSQL database              |PGSQL_README |Postfix 2.0 |
-    |__________________________________|_____________|____________|
-    |SASL authentication              |SASL_README  |Postfix 1.0 |
-    |__________________________________|_____________|____________|
-    |SQLite database                  |SQLITE_README|Postfix 2.8 |
-    |__________________________________|_____________|____________|
-    |STARTTLS session encryption       |TLS_README   |Postfix 2.2 |
-    |__________________________________|_____________|____________|
+     ______________________________________________________________
+    |Optional feature                 |Document      |Availability|
+    |__________________________________|______________|____________|
+    |Berkeley DB database             |DB_README     |Postfix 1.0 |
+    |__________________________________|______________|____________|
+    |LMDB database                    |LMDB_README   |Postfix 2.11|
+    |__________________________________|______________|____________|
+    |LDAP database                    |LDAP_README   |Postfix 1.0 |
+    |__________________________________|______________|____________|
+    |MongoDB database                 |MONGODB_README|Postfix 3.9 |
+    |__________________________________|______________|____________|
+    |MySQL database                   |MYSQL_README  |Postfix 1.0 |
+    |__________________________________|______________|____________|
+    |Perl compatible regular expression|PCRE_README   |Postfix 1.0 |
+    |__________________________________|______________|____________|
+    |PostgreSQL database              |PGSQL_README  |Postfix 2.0 |
+    |__________________________________|______________|____________|
+    |SASL authentication              |SASL_README   |Postfix 1.0 |
+    |__________________________________|______________|____________|
+    |SQLite database                  |SQLITE_README |Postfix 2.8 |
+    |__________________________________|______________|____________|
+    |STARTTLS session encryption       |TLS_README    |Postfix 2.2 |
+    |__________________________________|______________|____________|
 
 Note: IP version 6 support is compiled into Postfix on operating systems that
 have IPv6 support. See the IPV6_README file for details.
index 9afa3b7d26e75d464862ef21b3d38799a8b3b8f0..6bef06dec821bc626196659e1c956c6a7a6fe4ea 100644 (file)
@@ -52,6 +52,7 @@ L\bLo\boo\bok\bku\bup\bp t\bta\bab\bbl\ble\bes\bs (\b(d\bda\bat\bta\bab\bba\bas\bse\bes\bs)\b)
   * LDAP_README: LDAP Howto
   * LMDB_README: LMDB Howto
   * MEMCACHE_README: Memcache Howto
+  * MONGODB_README: MongoDB Howto
   * MYSQL_README: MySQL Howto
   * PCRE_README: PCRE Howto
   * PGSQL_README: PostgreSQL Howto
index edf7c588fe3689e1104bf0cb6de92207a3d5db5b..f1629e925910a41ed0bab02126e9fdfa53beae5f 100644 (file)
@@ -236,6 +236,9 @@ To find out what database types your Postfix system supports, use the "p\bpo\bos\bs
     m\bme\bem\bmc\bca\bac\bch\bhe\be
         Memcache database client. Configuration details are given in
         memcache_table(5).
+    m\bmo\bon\bng\bgo\bod\bdb\bb (read-only)
+        MongoDB database client. Configuration details are given in
+        mongodb_table(5), with examples in MONGODB_README.
     m\bmy\bys\bsq\bql\bl (read-only)
         MySQL database client. Configuration details are given in mysql_table
         (5).
index 09d0f803ed06eec1e8a9d721be19bbdacd7282ab..85ed2cc6360e2cdf54f304606565cf74b67ec8c9 100644 (file)
@@ -376,27 +376,29 @@ whistles. Support for third-party databases etc. must be configured when
 Postfix is compiled. The following documents describe how to build Postfix with
 support for optional features:
 
-     _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b 
-    |O\bOp\bpt\bti\bio\bon\bna\bal\bl f\bfe\bea\bat\btu\bur\bre\be                  |D\bDo\boc\bcu\bum\bme\ben\bnt\bt     |A\bAv\bva\bai\bil\bla\bab\bbi\bil\bli\bit\bty\by|
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |Berkeley DB database              |DB_README    |Postfix 1.0 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |LMDB database                     |LMDB_README  |Postfix 2.11|
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |LDAP database                     |LDAP_README  |Postfix 1.0 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |MySQL database                    |MYSQL_README |Postfix 1.0 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |Perl compatible regular expression|PCRE_README  |Postfix 1.0 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |PostgreSQL database               |PGSQL_README |Postfix 2.0 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |SASL authentication               |SASL_README  |Postfix 1.0 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |SQLite database                   |SQLITE_README|Postfix 2.8 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
-    |STARTTLS session encryption       |TLS_README   |Postfix 2.2 |
-    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+     _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b 
+    |O\bOp\bpt\bti\bio\bon\bna\bal\bl f\bfe\bea\bat\btu\bur\bre\be                  |D\bDo\boc\bcu\bum\bme\ben\bnt\bt      |A\bAv\bva\bai\bil\bla\bab\bbi\bil\bli\bit\bty\by|
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |Berkeley DB database              |DB_README     |Postfix 1.0 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |LMDB database                     |LMDB_README   |Postfix 2.11|
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |LDAP database                     |LDAP_README   |Postfix 1.0 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |MongoDB database                  |MONGODB_README|Postfix 3.9 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |MySQL database                    |MYSQL_README  |Postfix 1.0 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |Perl compatible regular expression|PCRE_README   |Postfix 1.0 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |PostgreSQL database               |PGSQL_README  |Postfix 2.0 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |SASL authentication               |SASL_README   |Postfix 1.0 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |SQLite database                   |SQLITE_README |Postfix 2.8 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |STARTTLS session encryption       |TLS_README    |Postfix 2.2 |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
 
 Note: IP version 6 support is compiled into Postfix on operating systems that
 have IPv6 support. See the IPV6_README file for details.
diff --git a/postfix/README_FILES/MONGODB_README b/postfix/README_FILES/MONGODB_README
new file mode 100644 (file)
index 0000000..aaeb71c
--- /dev/null
@@ -0,0 +1,169 @@
+P\bPo\bos\bst\btf\bfi\bix\bx M\bMo\bon\bng\bgo\boD\bDB\bB H\bHo\bow\bwt\bto\bo
+
+-------------------------------------------------------------------------------
+
+M\bMo\bon\bng\bgo\boD\bDB\bB S\bSu\bup\bpp\bpo\bor\brt\bt i\bin\bn P\bPo\bos\bst\btf\bfi\bix\bx
+
+Postfix can use MongoDB as a source for any of its lookups: aliases(5), virtual
+(5), canonical(5), etc. This allows you to keep information for your mail
+service in a replicated noSQL database with fine-grained access controls. By
+not storing it locally on the mail server, the administrators can maintain it
+from anywhere, and the users can control whatever bits of it you think
+appropriate. You can have multiple mail servers using the same information,
+without the hassle and delay of having to copy it to each.
+
+Topics covered in this document:
+
+  * Building Postfix with MongoDB support
+  * Configuring MongoDB lookups
+  * Example: virtual alias maps
+  * Example: Mailing lists
+  * Example: MongoDB projections
+  * Feedback
+
+B\bBu\bui\bil\bld\bdi\bin\bng\bg P\bPo\bos\bst\btf\bfi\bix\bx w\bwi\bit\bth\bh M\bMo\bon\bng\bgo\boD\bDB\bB s\bsu\bup\bpp\bpo\bor\brt\bt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+The Postfix MongoDB client requires the mongo-c-driver library. This can be
+built from source code from the mongod-c project, or as a binary package from
+your OS distribution, typically named m\bmo\bon\bng\bgo\bo-\b-c\bc-\b-d\bdr\bri\biv\bve\ber\br-\b-d\bde\bev\bve\bel\bl or l\bli\bib\bbm\bmo\bon\bng\bgo\boc\bc-\b-d\bde\bev\bv.
+Installing the mongo-c-driver library may also install l\bli\bib\bbb\bbs\bso\bon\bn as a dependency.
+
+To build Postfix with mongodb map support, add to the CCARGS environment
+variable the options -DHAS_MONGODB and -I for the directory containing the
+mongodb headers, and specify the AUXLIBS_MONGODB with the libmongoc and libbson
+libraries, for example:
+
+    % make tidy
+    % make -f Makefile.init makefiles \
+        CCARGS="$CCARGS -DHAS_MONGODB -I/usr/include/libmongoc-1.0 \
+        -I/usr/include/libbson-1.0" \
+        AUXLIBS_MONGODB="-lmongoc-1.0 -lbson-1.0"
+
+The 'make tidy' command is needed only if you have previously built Postfix
+without MongoDB support.
+
+If your MongoDB shared library is in a directory that the RUN-TIME linker does
+not know about, add a "-Wl,-R,/path/to/directory" option after "-lbson-1.0".
+Then, just run 'make'.
+
+C\bCo\bon\bnf\bfi\big\bgu\bur\bri\bin\bng\bg M\bMo\bon\bng\bgo\boD\bDB\bB l\blo\boo\bok\bku\bup\bps\bs
+
+In order to use MongoDB lookups, define a MongoDB source as a table lookup in
+main.cf, for example:
+
+    alias_maps = hash:/etc/aliases, proxy:mongodb:/etc/postfix/mongo-aliases.cf
+
+The file /etc/postfix/mongo-aliases.cf can specify a number of parameters. For
+a complete description, see the mongodb_table(5) manual page.
+
+E\bEx\bxa\bam\bmp\bpl\ble\be:\b: v\bvi\bir\brt\btu\bua\bal\bl(\b(5\b5)\b) a\bal\bli\bia\bas\bs m\bma\bap\bps\bs
+
+Here's a basic example for using MongoDB to look up virtual(5) aliases. Assume
+that in main.cf, you have:
+
+    virtual_alias_maps = hash:/etc/postfix/virtual_aliases,
+        proxy:mongodb:/etc/postfix/mongo-virtual-aliases.cf
+
+and in mongodb:/etc/postfix/mongo-virtual-aliases.cf you have:
+
+    uri = mongodb+srv://user_name:password@some_server
+    dbname = mail
+    collection = mailbox
+    query_filter = {"$or": [{"username":"%s"}, {"alias.address": "%s"}],
+    "active": 1}
+    result_attribute = username
+
+This example assumes mailbox names are stored in a MongoDB backend, in a format
+like:
+
+    { "username": "user@example.com",
+      "alias": [
+        {"address": "admin@example.com"},
+        {"address": "abuse@example.com"}
+      ],
+      "active": 1
+    }
+
+Upon receiving mail for "admin@example.com" that isn't found in the /etc/
+postfix/virtual_aliases database, Postfix will search the MongoDB server/
+cluster listening at port 27017 on some_server. It will connect using the
+provided credentials, and search for any entries whose username is, or alias
+field has "admin@example.com". It will return the username attribute of those
+found, and build a list of their email addresses.
+
+E\bEx\bxa\bam\bmp\bpl\ble\be:\b: M\bMa\bai\bil\bli\bin\bng\bg l\bli\bis\bst\bts\bs
+
+When it comes to mailing lists, one way of implementing one would be as below:
+
+    { "name": "dev@example.com", "active": 1, "address":
+      [ "hamid@example.com", "wietse@example.com", "viktor@example.com" ] }
+
+using the filter below, will result in a comma separated string with all email
+addresses in this list.
+
+    query_filter = {"name": "%s", "active": 1}
+    result_attribute = address
+
+Notes:
+
+  * As with p\bpr\bro\boj\bje\bec\bct\bti\bio\bon\bn, the Postfix mongodb client automatically removes the
+    top-level '_id' field from a result.
+
+  * The Postfix mongodb client will only parse result fields with data types
+    UTF8, INT32, INT64 and ARRAY. Other fields will be ignored, with a warning
+    in the logs.
+
+E\bEx\bxa\bam\bmp\bpl\ble\be:\b: a\bad\bdv\bva\ban\bnc\bce\bed\bd p\bpr\bro\boj\bje\bec\bct\bti\bio\bon\bns\bs
+
+This module also supports the use of more complex MongoDB projections. There
+may be some use cases where operations such as concatenation are necessary to
+be performed on the data retrieved from the database. Although it is encouraged
+to keep the database design simple enough so this is not necessary, postfix
+supports the use of MongoDB projections to achieve the goal.
+
+Consider the example below:
+
+    { "username": "user@example.com",
+      "local_part": "user",
+      "domain": "example.com",
+      "alias": [
+        {"address": "admin@example.com"},
+        {"address": "abuse@example.com"}
+      ],
+      "active": 1
+    }
+
+virtual_mailbox_maps can be created using below parameters in a mongodb:/etc/
+postfix/mongo-virtual-mailboxes.cf file:
+
+    uri = mongodb+srv://user_name:password@some_server
+    dbname = mail
+    collection = mailbox
+    query_filter = {"$or": [{"username":"%s"}, {"alias.address": "%s"}],
+    "active": 1}
+    projection = { "mail_path": {"$concat": ["$domain", "/", "$local_part"]} }
+
+This will return 'example.com/user' path built from the database fields.
+
+A couple of considerations when using projections:
+
+  * As with r\bre\bes\bsu\bul\blt\bt_\b_a\bat\btt\btr\bri\bib\bbu\but\bte\be, the Postfix mongodb client automatically removes
+    the top-level '_id' field from a projection result.
+
+  * The Postfix mongodb client will only parse fields with data types UTF8,
+    INT32, INT64 and ARRAY. Other fields will be ignored, with a warning in the
+    logs. It is suggested to exclude any unnecessary fields when using a
+    projection.
+
+F\bFe\bee\bed\bdb\bba\bac\bck\bk
+
+If you have questions, send them to postfix-users@postfix.org. Please include
+relevant information about your Postfix setup: MongoDB-related output from
+postconf, which libraries you built with, and such. If your question involves
+your database contents, please include the applicable bits of some database
+entries.
+
index 5179f66f077c719894214bb1453163d8f1d23f16..feeb6a11e6be20604c63fa7cf3826f172eaca497 100644 (file)
@@ -2,6 +2,7 @@
 cdb    ${LIB_PREFIX}cdb${LIB_SUFFIX}   dict_cdb_open   mkmap_cdb_open
 ldap   ${LIB_PREFIX}ldap${LIB_SUFFIX}  dict_ldap_open
 lmdb   ${LIB_PREFIX}lmdb${LIB_SUFFIX}  dict_lmdb_open  mkmap_lmdb_open
+mongodb        ${LIB_PREFIX}mongodb${LIB_SUFFIX}       dict_mongodb_open
 mysql  ${LIB_PREFIX}mysql${LIB_SUFFIX} dict_mysql_open
 pcre   ${LIB_PREFIX}pcre${LIB_SUFFIX}  dict_pcre_open
 pgsql  ${LIB_PREFIX}pgsql${LIB_SUFFIX} dict_pgsql_open
index 12a7ccbc5097fb7d97d2d07eeff4b849c65f94e8..bbc4dcd212114ddbedfa331478c666d1a1995d0d 100644 (file)
@@ -75,6 +75,7 @@ $shlib_directory/lib${LIB_PREFIX}master${LIB_SUFFIX}:f:root:-:755
 $shlib_directory/${LIB_PREFIX}cdb${LIB_SUFFIX}:f:root:-:755
 $shlib_directory/${LIB_PREFIX}ldap${LIB_SUFFIX}:f:root:-:755
 $shlib_directory/${LIB_PREFIX}lmdb${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}mongodb${LIB_SUFFIX}:f:root:-:755
 $shlib_directory/${LIB_PREFIX}mysql${LIB_SUFFIX}:f:root:-:755
 $shlib_directory/${LIB_PREFIX}pcre${LIB_SUFFIX}:f:root:-:755
 $shlib_directory/${LIB_PREFIX}pgsql${LIB_SUFFIX}:f:root:-:755
@@ -194,6 +195,7 @@ $manpage_directory/man5/ldap_table.5:f:root:-:644
 $manpage_directory/man5/lmdb_table.5:f:root:-:644
 $manpage_directory/man5/master.5:f:root:-:644
 $manpage_directory/man5/memcache_table.5:f:root:-:644
+$manpage_directory/man5/mongodb_table.5:f:root:-:644
 $manpage_directory/man5/mysql_table.5:f:root:-:644
 $manpage_directory/man5/socketmap_table.5:f:root:-:644
 $manpage_directory/man5/sqlite_table.5:f:root:-:644
@@ -301,6 +303,7 @@ $readme_directory/MAILDROP_README:f:root:-:644
 $readme_directory/MAILLOG_README:f:root:-:644
 $readme_directory/MEMCACHE_README:f:root:-:644
 $readme_directory/MILTER_README:f:root:-:644
+$readme_directory/MONGODB_README:f:root:-:644
 $readme_directory/MULTI_INSTANCE_README:f:root:-:644
 $readme_directory/MYSQL_README:f:root:-:644
 $readme_directory/SMTPUTF8_README:f:root:-:644
@@ -362,6 +365,7 @@ $html_directory/MAILDROP_README.html:f:root:-:644
 $html_directory/MAILLOG_README.html:f:root:-:644
 $html_directory/MEMCACHE_README.html:f:root:-:644
 $html_directory/MILTER_README.html:f:root:-:644
+$html_directory/MONGODB_README.html:f:root:-:644
 $html_directory/MULTI_INSTANCE_README.html:f:root:-:644
 $html_directory/MYSQL_README.html:f:root:-:644
 $html_directory/SMTPUTF8_README.html:f:root:-:644
@@ -418,6 +422,7 @@ $html_directory/mailq.1.html:f:root:-:644
 $html_directory/master.5.html:f:root:-:644
 $html_directory/master.8.html:f:root:-:644
 $html_directory/memcache_table.5.html:f:root:-:644
+$html_directory/mongodb_table.5.html:f:root:-:644
 $html_directory/mysql_table.5.html:f:root:-:644
 $html_directory/sqlite_table.5.html:f:root:-:644
 $html_directory/nisplus_table.5.html:f:root:-:644
index 1f7b183c382fe382b1479ca3ae442c09fb7411f4..0e3e222879486dd17d7e88c1f7dcd0d708a5cd04 100644 (file)
@@ -349,6 +349,11 @@ See <a href="lmdb_table.5.html">lmdb_table(5)</a> for details.  </dd>
 <dd> Memcache database client. Configuration details are given in
 <a href="memcache_table.5.html">memcache_table(5)</a>. </dd>
 
+<dt> <b>mongodb</b> (read-only) </dt>
+
+<dd> MongoDB database client. Configuration details are given in
+mongodb_table(5), with examples in <a href="MONGODB_README.html">MONGODB_README</a>. </dd>
+
 <dt> <b>mysql</b> (read-only) </dt>
 
 <dd> MySQL database client. Configuration details are given in
index 6cd70d113773e5cc078a770ed43c97b15ef72030..94d78baa7c8c2431fd29517cb59d39a508ac0f8f 100644 (file)
@@ -605,6 +605,9 @@ describe how to build Postfix with support for optional features:
 <tr> <td> LDAP database</td> <td><a href="LDAP_README.html">LDAP_README</a></td> <td> Postfix
 1.0 </td> </tr>
 
+<tr> <td> MongoDB database</td> <td><a href="MONGODB_README.html">MONGODB_README</a></td> <td> Postfix
+3.9 </td> </tr>
+
 <tr> <td> MySQL database</td> <td><a href="MYSQL_README.html">MYSQL_README</a></td> <td> Postfix
 1.0 </td> </tr>
 
diff --git a/postfix/html/MONGODB_README.html b/postfix/html/MONGODB_README.html
new file mode 100644 (file)
index 0000000..278c0c8
--- /dev/null
@@ -0,0 +1,232 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<title>Postfix MongoDB Howto</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+</head>
+<body>
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix MongoDB Howto</h1>
+<hr>
+
+<h2>MongoDB Support in Postfix</h2>
+
+<p> Postfix can use MongoDB as a source for any of its lookups:
+<a href="aliases.5.html">aliases(5)</a>, <a href="virtual.5.html">virtual(5)</a>, <a href="canonical.5.html">canonical(5)</a>, etc. This allows you to keep
+information for your mail service in a replicated noSQL database
+with fine-grained access controls. By not storing it locally on the
+mail server, the administrators can maintain it from anywhere, and
+the users can control whatever bits of it you think appropriate.
+You can have multiple mail servers using the same information,
+without the hassle and delay of having to copy it to each. </p>
+
+<p> Topics covered in this document:</p>
+
+<ul>
+<li><a href="#build">Building Postfix with MongoDB support</a>
+<li><a href="#config">Configuring MongoDB lookups</a>
+<li><a href="#example_virtual">Example: virtual alias maps</a>
+<li><a href="#example_mailing_list">Example: Mailing lists</a>
+<li><a href="#example_projections">Example: MongoDB projections</a>
+<li><a href="#feedback">Feedback</a>
+</ul>
+
+<h2><a name="build">Building Postfix with MongoDB support</a></h2>
+
+<p>These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p>The Postfix MongoDB client requires the mongo-c-driver library.
+This can be built from source code from <a
+href="https://github.com/mongodb/mongo-c-driver/releases">the
+mongod-c project</a>, or as a binary package from your OS distribution,
+typically named <b>mongo-c-driver-devel</b> or <b>libmongoc-dev</b>.
+Installing the mongo-c-driver library may also install <b>libbson</b>
+as a dependency. </p>
+
+<p> To build Postfix with mongodb map support, add to the CCARGS
+environment variable the options -DHAS_MONGODB and -I for the
+directory containing the mongodb headers, and specify the <a href="MONGODB_README.html">AUXLIBS_MONGODB</a>
+with the libmongoc and libbson libraries, for example:</p>
+
+<blockquote>
+<pre>
+% make tidy
+% make -f Makefile.init makefiles \
+    CCARGS="$CCARGS -DHAS_MONGODB -I/usr/include/libmongoc-1.0 \
+    -I/usr/include/libbson-1.0" \
+    <a href="MONGODB_README.html">AUXLIBS_MONGODB</a>="-lmongoc-1.0 -lbson-1.0"
+</pre>
+</blockquote>
+
+<p>The 'make tidy' command is needed only if you have previously
+built Postfix without MongoDB support. </p>
+
+<p>If your MongoDB shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option
+after "-lbson-1.0". Then, just run 'make'.</p>
+
+<h2><a name="config">Configuring MongoDB lookups</a></h2>
+
+<p> In order to use MongoDB lookups, define a MongoDB source as a
+table lookup in <a href="postconf.5.html">main.cf</a>, for example: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases, <a href="proxymap.8.html">proxy</a>:<a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/mongo-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/mongo-aliases.cf can specify a number of
+parameters. For a complete description, see the mongodb_table(5)
+manual page. </p>
+
+<h2><a name="example_virtual">Example: virtual(5) alias maps</a></h2>
+
+<p> Here's a basic example for using MongoDB to look up <a href="virtual.5.html">virtual(5)</a>
+aliases. Assume that in <a href="postconf.5.html">main.cf</a>, you have: </p>
+
+<blockquote> 
+<pre>
+<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual_aliases, 
+    <a href="proxymap.8.html">proxy</a>:<a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/mongo-virtual-aliases.cf
+</pre>
+</blockquote> 
+
+<p> and in <a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/mongo-virtual-aliases.cf you have: </p>
+
+<blockquote> 
+<pre>
+uri = mongodb+srv://user_name:password@some_server
+dbname = mail
+collection = mailbox
+query_filter = {"$or": [{"username":"%s"}, {"alias.address": "%s"}], "active": 1}
+result_attribute = username
+</pre>
+</blockquote> 
+
+<p>This example assumes mailbox names are stored in a MongoDB backend,
+in a format like:</p>
+
+<blockquote> 
+<pre>
+{ "username": "user@example.com",
+  "alias": [
+    {"address": "admin@example.com"},
+    {"address": "abuse@example.com"}
+  ],
+  "active": 1
+}
+</pre>
+</blockquote> 
+
+<p>Upon receiving mail for "admin@example.com" that isn't found in the
+/etc/postfix/virtual_aliases database, Postfix will search the
+MongoDB server/cluster listening at port 27017 on some_server. It
+will connect using the provided credentials, and search for any
+entries whose username is, or alias field has "admin@example.com".
+It will return the username attribute of those found, and build a
+list of their email addresses. </p>
+
+<h2><a name="example_mailing_list">Example: Mailing lists</a></h2>
+
+<p>When it comes to mailing lists, one way of implementing one would
+be as below:</p>
+
+<blockquote> 
+<pre>
+{ "name": "dev@example.com", "active": 1, "address": 
+  [ "hamid@example.com", "wietse@example.com", "viktor@example.com" ] }
+</pre>
+</blockquote> 
+
+<p>using the filter below, will result in a comma separated string
+with all email addresses in this list. </p>
+
+<blockquote> 
+<pre>
+query_filter = {"name": "%s", "active": 1}
+result_attribute = address
+</pre>
+</blockquote> 
+
+<p> Notes: </p>
+
+<ul>
+
+<li><p> As with <b>projection</b>, the Postfix mongodb client
+automatically removes the top-level '_id' field from a result. </p>
+</li>
+
+<li><p> The Postfix mongodb client will only parse result fields
+with data types UTF8, INT32, INT64 and ARRAY. Other fields will be
+ignored, with a warning in the logs. </p> </li>
+
+</ul>
+
+<h2><a name="example_projections">Example: advanced projections</a></h2>
+
+<p>This module also supports the use of more complex MongoDB
+projections.  There may be some use cases where operations such as
+concatenation are necessary to be performed on the data retrieved
+from the database. Although it is encouraged to keep the database
+design simple enough so this is not necessary, postfix supports the
+use of MongoDB projections to achieve the goal. </p>
+
+<p>Consider the example below:</p>
+
+<blockquote> 
+<pre>
+{ "username": "user@example.com",
+  "local_part": "user",
+  "domain": "example.com",
+  "alias": [
+    {"address": "admin@example.com"},
+    {"address": "abuse@example.com"}
+  ],
+  "active": 1
+}
+</pre>
+</blockquote> 
+
+<p><a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> can be created using below parameters in a
+<a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/mongo-virtual-mailboxes.cf file:</p>
+
+<blockquote> 
+<pre>
+uri = mongodb+srv://user_name:password@some_server
+dbname = mail
+collection = mailbox
+query_filter = {"$or": [{"username":"%s"}, {"alias.address": "%s"}], "active": 1}
+projection = { "mail_path": {"$concat": ["$domain", "/", "$local_part"]} }
+</pre>
+</blockquote> 
+
+<p>This will return 'example.com/user' path built from the database fields. </p>
+
+<p>A couple of considerations when using projections:</p>
+
+<ul>
+
+<li><p>As with <b>result_attribute</b>, the Postfix mongodb client
+automatically removes the top-level '_id' field from a projection
+result. </p></li>
+
+<li><p> The Postfix mongodb client will only parse fields with data
+types UTF8, INT32, INT64 and ARRAY. Other fields will be ignored,
+with a warning in the logs. It is suggested to exclude any unnecessary
+fields when using a projection. </p></li>
+
+</ul>
+<h2><a name="feedback">Feedback</a></h2>
+
+<p> If you have questions, send them to postfix-users@postfix.org.
+Please include relevant information about your Postfix setup:
+MongoDB-related output from postconf, which libraries you built
+with, and such. If your question involves your database contents,
+please include the applicable bits of some database entries. </p>
+
+</body>
+
+</html>
index c5481f8aff7019e24da820781ebb10531e8be321..7f23ed7fdac975412bf7f0fbc2d79af3bf1fa46d 100644 (file)
@@ -20,7 +20,7 @@ CONFIG        = access.5.html aliases.5.html canonical.5.html relocated.5.html \
        transport.5.html virtual.5.html pcre_table.5.html regexp_table.5.html \
        cidr_table.5.html tcp_table.5.html header_checks.5.html \
        ldap_table.5.html lmdb_table.5.html mysql_table.5.html \
-       pgsql_table.5.html memcache_table.5.html \
+       pgsql_table.5.html memcache_table.5.html mongodb_table.5.html \
        master.5.html nisplus_table.5.html generic.5.html bounce.5.html \
        postfix-wrapper.5.html sqlite_table.5.html socketmap_table.5.html
 OTHER  = postfix-manuals.html
@@ -298,6 +298,10 @@ memcache_table.5.html: ../proto/memcache_table
        PATH=../mantools:$$PATH; \
        srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
 
+mongodb_table.5.html: ../proto/mongodb_table
+       PATH=../mantools:$$PATH; \
+       srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
 mysql_table.5.html: ../proto/mysql_table
        PATH=../mantools:$$PATH; \
        srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
index 68edc59b3f5900752d1036a03598bdf56f64332a..fe1bfab98a8bfc1f1c381813cdecfa2f2d9ba013 100644 (file)
@@ -141,6 +141,8 @@ Per-client/user/etc. access </a>
 
 <li> <a href="MEMCACHE_README.html"> Memcache Howto  </a>
 
+<li> <a href="MONGODB_README.html"> MongoDB Howto  </a>
+
 <li> <a href="MYSQL_README.html"> MySQL Howto  </a>
 
 <li> <a href="PCRE_README.html"> PCRE Howto </a>
index 7202a8df9894d0656eee8435c1e291bd5a50ab57..ce6725713b148c82a1d53c2d054792ee7f5d6005 100644 (file)
@@ -34,9 +34,9 @@ MAKEDEFS(1)                                                        MAKEDEFS(1)
        <b>AUXLIBS=</b><i>object</i><b>_</b><i>library...</i>
               Specifies  one or more non-default object libraries. Postfix 3.0
               and later specify some of their  database  library  dependencies
-              with  <a href="CDB_README.html">AUXLIBS_CDB</a>,  <a href="LDAP_README.html">AUXLIBS_LDAP</a>,  <a href="LMDB_README.html">AUXLIBS_LMDB</a>,  <a href="MYSQL_README.html">AUXLIBS_MYSQL</a>,
-              <a href="PCRE_README.html">AUXLIBS_PCRE</a>, <a href="PGSQL_README.html">AUXLIBS_PGSQL</a>, AUXLIBS_SDBM,  and  <a href="SQLITE_README.html">AUXLIBS_SQLITE</a>,
-              respectively.
+              with  <a href="CDB_README.html">AUXLIBS_CDB</a>,  <a href="LDAP_README.html">AUXLIBS_LDAP</a>, <a href="LMDB_README.html">AUXLIBS_LMDB</a>, <a href="MONGODB_README.html">AUXLIBS_MONGODB</a>,
+              <a href="MYSQL_README.html">AUXLIBS_MYSQL</a>, <a href="PCRE_README.html">AUXLIBS_PCRE</a>,  <a href="PGSQL_README.html">AUXLIBS_PGSQL</a>,  AUXLIBS_SDBM,  and
+              <a href="SQLITE_README.html">AUXLIBS_SQLITE</a>, respectively.
 
        <b>CC=</b><i>compiler</i><b>_</b><i>command</i>
               Specifies  a  non-default compiler. On many systems, the default
diff --git a/postfix/html/mongodb_table.5.html b/postfix/html/mongodb_table.5.html
new file mode 100644 (file)
index 0000000..b7434f2
--- /dev/null
@@ -0,0 +1,215 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<link rel='stylesheet' type='text/css' href='postfix-doc.css'>
+<title> Postfix manual - mongodb_table(5) </title>
+</head> <body> <pre>
+MONGODB_TABLE(5)                                              MONGODB_TABLE(5)
+
+<b>NAME</b>
+       mongodb_table - Postfix MongoDB client configuration
+
+<b>SYNOPSIS</b>
+       <b>postmap -q "</b><i>string</i><b>" <a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/</b><i>filename</i>
+
+       <b>postmap -q - <a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+       The  Postfix  mail system uses optional tables for address rewriting or
+       mail routing. These tables are usually in <b>dbm</b> or <b>db</b> format.
+
+       Alternatively, lookup tables can be specified as MongoDB databases.  In
+       order to use MongoDB lookups, define a MongoDB source as a lookup table
+       in <a href="postconf.5.html">main.cf</a>, for example:
+           <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/mongodb-aliases.cf
+
+       In this example, the file /etc/postfix/mongodb-aliases.cf has the  same
+       format  as  the  Postfix  <a href="postconf.5.html">main.cf</a>  file, and can specify the parameters
+       described below. It is also  possible  to  have  the  configuration  in
+       <a href="postconf.5.html">main.cf</a>; see "OBSOLETE MAIN.CF PARAMETERS" below.
+
+       It is strongly recommended to use <a href="proxymap.8.html">proxy</a>:mongodb, in order to reduce the
+       number of database connections. For example:
+           <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="proxymap.8.html">proxy</a>:<a href="mongodb_table.5.html">mongodb</a>:/etc/postfix/mongodb-aliases.cf
+
+       Note: when using <a href="proxymap.8.html">proxy</a>:<a href="mongodb_table.5.html">mongodb</a>:/<i>file</i>, the file must be readable by  the
+       unprivileged  postfix  user (specified with the Postfix <a href="postconf.5.html#mail_owner">mail_owner</a> con-
+       figuration parameter).
+
+<b>MONGODB PARAMETERS</b>
+       <b>uri</b>    The URI of mongo server/cluster that Postfix will try to connect
+              to and query from. Please see
+              <a href="https://www.mongodb.com/docs/manual/reference/connection-string/">https://www.mongodb.com/docs/manual/reference/connection-string/</a>
+
+              Example:
+                  uri = mongodb+srv://user:pass@loclhost:27017/mail
+
+       <b>dbname</b> Name of the database to read the information from.  Example:
+                  dbname = mail
+
+       <b>collection</b>
+              Name  of  the  collection  (table) to read the information from.
+              Example:
+                  collection = mailbox
+
+       <b>query_filter</b>
+              The MongoDB query template used to search the database, where <b>%s</b>
+              is  a substitute for the email address that Postfix is trying to
+              resolve. Please see:
+              <a href="https://www.mongodb.com/docs/manual/tutorial/query-documents/">https://www.mongodb.com/docs/manual/tutorial/query-documents/</a>
+
+              Example:
+                  query_filter = {"$or": [{"username": "%s"}, {"alias.address": "%s"}], "active": 1}
+
+              This parameter supports the following '%' expansions:
+
+              <b>%%</b>     This is replaced by a literal '%' character.
+
+              <b>%s</b>     This is replaced by the input key. The %s must appear  in
+                     quotes,  because all Postfix queries are strings contain-
+                     ing (parts from) a domain or email address. Postfix makes
+                     no numerical queries.
+
+              <b>%u</b>     When the input key is an address of the form user@domain,
+                     <b>%u</b> is replaced by the local part of the address.   Other-
+                     wise, <b>%u</b> is replaced by the entire search string.
+
+              <b>%d</b>     When the input key is an address of the form user@domain,
+                     <b>%d</b> is replaced by the domain part of the address.
+
+              <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced  by  the  corre-
+                     sponding  most  significant  component of the input key's
+                     domain. If the input key is  <i>user@mail.example.com</i>,  then
+                     %1 is <b>com</b>, %2 is <b>example</b> and %3 is <b>mail</b>.
+
+              In  the  above  substitutions,  characters  will  be  quoted  as
+              required by <a href="https://tools.ietf.org/html/rfc4627">RFC 4627</a>. For example, each double  quote  or  back-
+              slash character will be escaped with a backslash characacter.
+
+       <b>projection</b>
+              Advanced MongoDB query projections. Please see:
+              <a href="https://www.mongodb.com/docs/manual/tutorial/project-fields-from-query-results/">https://www.mongodb.com/docs/manual/tutorial/project-fields-from-query-results/</a>
+
+              <b>o</b>      If <b>projection</b> is non-empty, then <b>result_attribute</b> must be
+                     empty.
+
+              <b>o</b>      This implementation can  extract  information  only  from
+                     result  fields  that  have  type  <b>string</b>  (UTF8), <b>integer</b>
+                     (int32, int64) and <b>array</b>. Other  result  fields  will  be
+                     ignored with a warning. Please see:
+                     <a href="https://mongoc.org/libbson/current/bson_type_t.html">https://mongoc.org/libbson/current/bson_type_t.html</a>
+
+              <b>o</b>      As  with  <b>result_attribute</b>, the top-level _id field (type
+                     OID) is automatically removed from projection results.
+
+       <b>result_attribute</b>
+              Comma or whitespace separated list with the names of  fields  to
+              be returned in a lookup result.
+
+              <b>o</b>      If <b>result_attribute</b> is non-empty, then <b>projection</b> must be
+                     empty.
+
+              <b>o</b>      As with <b>projection</b>, the top-level _id field (type OID) is
+                     automatically removed from lookup results.
+
+       <b>result_format (default: %s</b>)
+              Format  template  applied  to  the  result  from  <b>projection</b>  or
+              <b>result_attribute</b>. Most commonly used to append (or prepend) text
+              to  the result. This parameter supports the following '%' expan-
+              sions:
+
+              <b>%%</b>     This is replaced by a literal '%' character.
+
+              <b>%s</b>     This is replaced by the value of  the  result  attribute.
+                     When result is empty it is skipped.
+
+              <b>%u</b>     When the result attribute value is an address of the form
+                     user@domain, <b>%u</b> is replaced by  the  local  part  of  the
+                     address.  When  the  result  has an empty localpart it is
+                     skipped.
+
+              <b>%d</b>     When a result attribute value is an address of  the  form
+                     user@domain,  <b>%d</b>  is  replaced  by the domain part of the
+                     attribute value. When the result  is  unqualified  it  is
+                     skipped.
+
+              <b>%[SUD1-9]</b>
+                     The  upper-case  and decimal digit expansions interpolate
+                     the parts of the input key rather than the result.  Their
+                     behavior  is  identical to that described with <b>query_fil-</b>
+                     <b>ter</b>, and in fact  because  the  input  key  is  known  in
+                     advance,  lookups  whose  key  does  not  contain all the
+                     information specified in the  result  template  are  sup-
+                     pressed and return no results.
+
+              For example, using "result_format = <a href="smtp.8.html">smtp</a>:[%s]" allows one to use
+              a mailHost attribute as the basis of a <a href="transport.5.html">transport(5)</a> table. After
+              applying  the result format, multiple values are concatenated as
+              comma separated strings. The expansion_limit parameter explained
+              below allows one to restrict the number of values in the result,
+              which is especially useful for maps that should return a  single
+              value.
+
+              The  default value <b>%s</b> specifies that each attribute value should
+              be used as is.
+
+              NOTE: DO NOT put quotes around the result format! The result  is
+              not a JSON string.
+
+       <b>domain (default: no domain list)</b>
+              This  is a list of domain names, paths to files, or "<a href="DATABASE_README.html">type:table</a>"
+              databases. When specified, only fully qualified search keys with
+              a  *non-empty*  localpart and a matching domain are eligible for
+              lookup:  'user'  lookups,  bare  domain  lookups  and  "@domain"
+              lookups  are  not  performed.  This can significantly reduce the
+              query load on the backend database. Example:
+                  domain = postfix.org, <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/searchdomains
+
+       <b>expansion_limit (default: 0)</b>
+              A limit on the total number of result elements  returned  (as  a
+              comma separated list) by a lookup against the map.  A setting of
+              zero disables the limit. Lookups fail with a temporary error  if
+              the  limit  is  exceeded.  Setting  the  limit to 1 ensures that
+              lookups do not return multiple values.
+
+<b>OBSOLETE MAIN.CF PARAMETERS</b>
+       MongoDB parameters can also be defined in <a href="postconf.5.html">main.cf</a>. Specify  as  MongoDB
+       source  a  name  that  doesn't begin with a slash or a dot. The MongoDB
+       parameters will then be accessible as the name you've given the  source
+       in  its  definition,  an underscore, and the name of the parameter. For
+       example, if a map is specified as "<a href="mongodb_table.5.html">mongodb</a>:<i>mongodb</i><b>_</b><i>source</i>",  the  "uri"
+       parameter would be defined in <a href="postconf.5.html">main.cf</a> as "<i>mongodb</i><b>_</b><i>source</i>_uri".
+
+       Note:  with  this form, passwords are written in <a href="postconf.5.html">main.cf</a>, which is nor-
+       mally world-readable, and '$' in a mongodb parameter setting  needs  to
+       be written as '$$'.
+
+<b>SEE ALSO</b>
+       <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table maintenance
+       <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>README FILES</b>
+       <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+       <a href="MONGODB_README.html">MONGODB_README</a>, Postfix MONGODB client guide
+
+<b>LICENSE</b>
+       The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+       MongoDB support was introduced with Postfix version 3.9.
+
+<b>AUTHOR(S)</b>
+       Hamid Maadani (hamid@dexo.tech)
+       Dextrous Technologies, LLC
+
+       Edited by:
+       Wietse Venema
+       porcupine.org
+
+       Based on prior work by:
+       Stephan Ferraro
+       Aionda GmbH
+
+                                                              MONGODB_TABLE(5)
+</pre> </body> </html>
index 936ecae8f427215e8804590523bb12502ff5e561..84774a52bfe575db0beb94f6f1c5a33799d6d903 100644 (file)
@@ -164,6 +164,8 @@ the following convention:  </p>
 
 <li> <a href="memcache_table.5.html">memcache_table(5)</a>, Postfix memcache client 
 
+<li> <a href="mongodb_table.5.html">mongodb_table(5)</a>, Postfix MongoDB client 
+
 <li> <a href="mysql_table.5.html">mysql_table(5)</a>, Postfix MYSQL client 
 
 <li> <a href="nisplus_table.5.html">nisplus_table(5)</a>, Postfix NIS+ client 
index 90751ab55740ded32404343c7d62e40a3370474f..a6ede786f162ce3396e9938ece4f5b83f8606777 100644 (file)
@@ -359,6 +359,7 @@ POSTFIX(1)                                                          POSTFIX(1)
        <a href="ldap_table.5.html">ldap_table(5)</a>, Postfix LDAP client
        <a href="lmdb_table.5.html">lmdb_table(5)</a>, Postfix LMDB database driver
        <a href="memcache_table.5.html">memcache_table(5)</a>, Postfix memcache client
+       <a href="mongodb_table.5.html">mongodb_table(5)</a>, Postfix MongoDB client
        <a href="mysql_table.5.html">mysql_table(5)</a>, Postfix MYSQL client
        <a href="nisplus_table.5.html">nisplus_table(5)</a>, Postfix NIS+ client
        <a href="pcre_table.5.html">pcre_table(5)</a>, Associate PCRE pattern with value
index 98c8267357e64606bba6bb9c834005e4910f228c..430324d4ee92d12b4c33c8a4dc2caea0a3e98094 100644 (file)
@@ -35,6 +35,7 @@
 #      Specifies one or more non-default object libraries. Postfix
 #      3.0 and later specify some of their database library
 #      dependencies with AUXLIBS_CDB, AUXLIBS_LDAP, AUXLIBS_LMDB,
+#      AUXLIBS_MONGODB,
 #      AUXLIBS_MYSQL, AUXLIBS_PCRE, AUXLIBS_PGSQL, AUXLIBS_SDBM,
 #      and AUXLIBS_SQLITE, respectively.
 # .IP \fBCC=\fIcompiler_command\fR
@@ -1245,7 +1246,7 @@ DEFINED_MAP_TYPES=`
 
 # Propagate AUXLIBS_FOO or merge them into global AUXLIBS (i.e. SYSLIBS).
 
-PLUGGABLE_MAPS="CDB LDAP LMDB MYSQL PCRE PGSQL SDBM SQLITE"
+PLUGGABLE_MAPS="CDB LDAP LMDB MONGODB MYSQL PCRE PGSQL SDBM SQLITE"
 
 case "$dynamicmaps" in
 yes) for name in $PLUGGABLE_MAPS
index f98402ca9a8844f32deb3ff3a301e28af364b022..40a52363cd4c77acd3c05b5699a186b8f414fecd 100644 (file)
@@ -17,7 +17,7 @@ CONFIG        = man5/access.5 man5/aliases.5 man5/canonical.5 man5/relocated.5 \
        man5/transport.5 man5/virtual.5 man5/pcre_table.5 man5/regexp_table.5 \
        man5/cidr_table.5 man5/tcp_table.5 man5/header_checks.5 \
        man5/body_checks.5 man5/ldap_table.5 man5/lmdb_table.5 \
-       man5/memcache_table.5 man5/mysql_table.5 \
+       man5/memcache_table.5 man5/mongodb_table.5 man5/mysql_table.5 \
        man5/pgsql_table.5 man5/master.5 man5/nisplus_table.5 \
        man5/generic.5 man5/bounce.5 man5/postfix-wrapper.5 \
        man5/sqlite_table.5 man5/socketmap_table.5
@@ -316,6 +316,11 @@ man5/memcache_table.5: ../proto/memcache_table
            (cmp -s junk $? || mv junk $?) && rm -f junk
        ../mantools/srctoman - $? >$@
 
+man5/mongodb_table.5: ../proto/mongodb_table
+       ../mantools/fixman ../proto/postconf.proto $? >junk && \
+           (cmp -s junk $? || mv junk $?) && rm -f junk
+       ../mantools/srctoman - $? >$@
+
 man5/mysql_table.5: ../proto/mysql_table
        ../mantools/fixman ../proto/postconf.proto $? >junk && \
            (cmp -s junk $? || mv junk $?) && rm -f junk
index 70c848e0d4c0a4b16cbaefb009cc83131e41c620..c921ac25881a3a35ac1eec90feed32ae49ca6cc5 100644 (file)
@@ -38,6 +38,7 @@ of the make(1) command.
 Specifies one or more non\-default object libraries. Postfix
 3.0 and later specify some of their database library
 dependencies with AUXLIBS_CDB, AUXLIBS_LDAP, AUXLIBS_LMDB,
+AUXLIBS_MONGODB,
 AUXLIBS_MYSQL, AUXLIBS_PCRE, AUXLIBS_PGSQL, AUXLIBS_SDBM,
 and AUXLIBS_SQLITE, respectively.
 .IP \fBCC=\fIcompiler_command\fR
index fc690bed74da8426491d1ece12e54b9eab795c25..da39dc5cae6d7728e67eced488c4b8e78a5b9cfb 100644 (file)
@@ -330,6 +330,7 @@ cidr_table(5), Associate CIDR pattern with value
 ldap_table(5), Postfix LDAP client
 lmdb_table(5), Postfix LMDB database driver
 memcache_table(5), Postfix memcache client
+mongodb_table(5), Postfix MongoDB client
 mysql_table(5), Postfix MYSQL client
 nisplus_table(5), Postfix NIS+ client
 pcre_table(5), Associate PCRE pattern with value
diff --git a/postfix/man/man5/mongodb_table.5 b/postfix/man/man5/mongodb_table.5
new file mode 100644 (file)
index 0000000..cfbedf3
--- /dev/null
@@ -0,0 +1,259 @@
+.TH MONGODB_TABLE 5 
+.ad
+.fi
+.SH NAME
+mongodb_table
+\-
+Postfix MongoDB client configuration
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" mongodb:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- mongodb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting or mail routing. These tables are usually in
+\fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified as MongoDB
+databases.  In order to use MongoDB lookups, define a MongoDB
+source as a lookup table in main.cf, for example:
+.nf
+    alias_maps = mongodb:/etc/postfix/mongodb\-aliases.cf
+.fi
+
+In this example, the file /etc/postfix/mongodb\-aliases.cf
+has the same format as the Postfix main.cf file, and can
+specify the parameters described below. It is also possible
+to have the configuration in main.cf; see "OBSOLETE MAIN.CF
+PARAMETERS" below.
+
+It is strongly recommended to use proxy:mongodb, in order
+to reduce the number of database connections. For example:
+.nf
+    alias_maps = proxy:mongodb:/etc/postfix/mongodb\-aliases.cf
+.fi
+
+Note: when using proxy:mongodb:/\fIfile\fR, the file must
+be readable by the unprivileged postfix user (specified
+with the Postfix mail_owner configuration parameter).
+.SH "MONGODB PARAMETERS"
+.na
+.nf
+.ad
+.fi
+.IP "\fBuri\fR"
+The URI of mongo server/cluster that Postfix will try to
+connect to and query from. Please see
+.nf
+https://www.mongodb.com/docs/manual/reference/connection\-string/
+.fi
+
+Example:
+.nf
+    uri = mongodb+srv://user:pass@loclhost:27017/mail
+.fi
+.IP "\fBdbname\fR"
+Name of the database to read the information from.
+Example:
+.nf
+    dbname = mail
+.fi
+.IP "\fBcollection\fR"
+Name of the collection (table) to read the information from.
+Example:
+.nf
+    collection = mailbox
+.fi
+.IP "\fBquery_filter\fR"
+The MongoDB query template used to search the database,
+where \fB%s\fR is a substitute for the email address that
+Postfix is trying to resolve. Please see:
+.nf
+https://www.mongodb.com/docs/manual/tutorial/query\-documents/
+.fi
+
+Example:
+.nf
+    query_filter = {"$or": [{"username": "%s"}, {"alias.address": "%s"}], "active": 1}
+.fi
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the input key. The %s must appear in
+quotes, because all Postfix queries are strings containing
+(parts from) a domain or email address. Postfix makes no
+numerical queries.
+.IP "\fB%u\fR"
+When the input key is an address of the form user@domain,
+\fB%u\fR is replaced by the local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+.IP "\fB%d\fR"
+When the input key is an address of the form user@domain,
+\fB%d\fR is replaced by the domain part of the address.
+.IP "\fB%[1\-9]\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If
+the input key is \fIuser@mail.example.com\fR, then %1 is
+\fBcom\fR, %2 is \fBexample\fR and %3 is \fBmail\fR.
+.RE
+.IP
+In the above substitutions, characters will be quoted as
+required by RFC 4627. For example, each double quote or
+backslash character will be escaped with a backslash
+characacter.
+.IP "\fBprojection\fR"
+Advanced MongoDB query projections. Please see:
+.nf
+https://www.mongodb.com/docs/manual/tutorial/project\-fields\-from\-query\-results/
+.fi
+
+.RS
+.IP \(bu
+If \fBprojection\fR is non\-empty, then \fBresult_attribute\fR
+must be empty.
+.IP \(bu
+This implementation can extract information only from result
+fields that have type \fBstring\fR (UTF8), \fBinteger\fR
+(int32, int64) and \fBarray\fR. Other result fields will
+be ignored with a warning. Please see:
+.nf
+https://mongoc.org/libbson/current/bson_type_t.html
+.fi
+.IP \(bu
+As with \fBresult_attribute\fR, the top\-level _id field
+(type OID) is automatically removed from projection results.
+.RE
+.IP "\fBresult_attribute\fR"
+Comma or whitespace separated list with the names of fields
+to be returned in a lookup result.
+
+.RS
+.IP \(bu
+If \fBresult_attribute\fR is non\-empty, then \fBprojection\fR
+must be empty.
+.IP \(bu
+As with \fBprojection\fR, the top\-level _id field (type
+OID) is automatically removed from lookup results.
+.RE
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Format template applied to the result from \fBprojection\fR
+or \fBresult_attribute\fR. Most commonly used to append (or
+prepend) text to the result. This parameter supports the
+following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the value of the result attribute. When
+result is empty it is skipped.
+.IP "\fB%u\fR
+When the result attribute value is an address of the form
+user@domain, \fB%u\fR is replaced by the local part of the
+address. When the result has an empty localpart it is
+skipped.
+.IP "\fB%d\fR"
+When a result attribute value is an address of the form
+user@domain, \fB%d\fR is replaced by the domain part of the
+attribute value. When the result is unqualified it is
+skipped.
+.IP "\fB%[SUD1\-9]\fR"
+The upper\-case and decimal digit expansions interpolate the
+parts of the input key rather than the result. Their behavior
+is identical to that described with \fBquery_filter\fR, and
+in fact because the input key is known in advance, lookups
+whose key does not contain all the information specified
+in the result template are suppressed and return no results.
+.RE
+.IP
+For example, using "result_format = smtp:[%s]" allows one
+to use a mailHost attribute as the basis of a transport(5)
+table. After applying the result format, multiple values
+are concatenated as comma separated strings. The expansion_limit
+parameter explained below allows one to restrict the number
+of values in the result, which is especially useful for
+maps that should return a single value.
+
+The default value \fB%s\fR specifies that each
+attribute value should be used as is.
+
+NOTE: DO NOT put quotes around the result format! The result
+is not a JSON string.
+.IP "\fBdomain (default: no domain list)\fR"
+This is a list of domain names, paths to files, or "type:table"
+databases. When specified, only fully qualified search keys
+with a *non\-empty* localpart and a matching domain are
+eligible for lookup: 'user' lookups, bare domain lookups
+and "@domain" lookups are not performed. This can significantly
+reduce the query load on the backend database. Example:
+.nf
+    domain = postfix.org, hash:/etc/postfix/searchdomains
+.fi
+.IP "\fBexpansion_limit (default: 0)\fR"
+A limit on the total number of result elements returned (as
+a comma separated list) by a lookup against the map.  A
+setting of zero disables the limit. Lookups fail with a
+temporary error if the limit is exceeded. Setting the limit
+to 1 ensures that lookups do not return multiple values.
+.SH "OBSOLETE MAIN.CF PARAMETERS"
+.na
+.nf
+.ad
+.fi
+MongoDB parameters can also be defined in main.cf. Specify
+as MongoDB source a name that doesn't begin with a slash
+or a dot. The MongoDB parameters will then be accessible
+as the name you've given the source in its definition, an
+underscore, and the name of the parameter. For example, if
+a map is specified as "mongodb:\fImongodb_source\fR", the
+"uri" parameter would be defined in main.cf as
+"\fImongodb_source\fR_uri".
+
+Note: with this form, passwords are written in main.cf,
+which is normally world\-readable, and '$' in a mongodb
+parameter setting needs to be written as '$$'.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table maintenance
+postconf(5), configuration parameters
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+MONGODB_README, Postfix MONGODB client guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+MongoDB support was introduced with Postfix version 3.9.
+.SH "AUTHOR(S)"
+.na
+.nf
+Hamid Maadani (hamid@dexo.tech)
+Dextrous Technologies, LLC
+
+Edited by:
+Wietse Venema
+porcupine.org
+
+Based on prior work by:
+Stephan Ferraro
+Aionda GmbH
index 783836108bd11444f4e75651c9d5db0f7d68d2e4..6ca24c63dc659801e6de61881ce69a6b2ab3e910 100755 (executable)
@@ -895,6 +895,7 @@ while (<>) {
     s/[<bB>]*lmdb[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="lmdb_table.5.html">$&<\/a>/g;
     s/[<bB>]*mas[-<\/bB>]*\n* *[<bB>]*ter[<\/bB>]*\(5\)/<a href="master.5.html">$&<\/a>/g;
     s/[<bB>]*mem[-<\/bB>]*\n* *[<bB>]*cache[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="memcache_table.5.html">$&<\/a>/g;
+    s/[<bB>]*mongodb[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="mongodb_table.5.html">$&<\/a>/g;
     s/[<bB>]*mysql[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="mysql_table.5.html">$&<\/a>/g;
     s/[<bB>]*nisplus[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="nisplus_table.5.html">$&<\/a>/g;
     s/[<bB>]*pcre[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="pcre_table.5.html">$&<\/a>/g;
@@ -1252,6 +1253,7 @@ while (<>) {
     s/\b(ldap[is]?):/<a href="ldap_table.5.html">$1<\/a>:/g;
     s/\b(lmdb):/<a href="lmdb_table.5.html">$1<\/a>:/g;
     s/\b(memcache):/<a href="memcache_table.5.html">$1<\/a>:/g;
+    s/\b(mongodb):/<a href="mongodb_table.5.html">$1<\/a>:/g;
     s/\b(mysql):/<a href="mysql_table.5.html">$1<\/a>:/g;
     s/\b(nisplus):/<a href="nisplus_table.5.html">$1<\/a>:/g;
     s/\b(pcre):/<a href="pcre_table.5.html">$1<\/a>:/g;
index cb25f35bb5e1de3eb2ab992ed8bbc4e7fba61e62..e9d0c189c6d6886abbb2a220a41b6a7d45663448 100644 (file)
@@ -349,6 +349,11 @@ ldap_table(5). </dd>
 <dd> Memcache database client. Configuration details are given in
 memcache_table(5). </dd>
 
+<dt> <b>mongodb</b> (read-only) </dt>
+
+<dd> MongoDB database client. Configuration details are given in
+mongodb_table(5), with examples in MONGODB_README. </dd>
+
 <dt> <b>mysql</b> (read-only) </dt>
 
 <dd> MySQL database client. Configuration details are given in
index 4686f030e88d7f4967997b2fb6dd4b5f0df65ea4..50249b28d59cc51b52a51db5a046bdadd8b14d63 100644 (file)
@@ -605,6 +605,9 @@ describe how to build Postfix with support for optional features:
 <tr> <td> LDAP database</td> <td>LDAP_README</td> <td> Postfix
 1.0 </td> </tr>
 
+<tr> <td> MongoDB database</td> <td>MONGODB_README</td> <td> Postfix
+3.9 </td> </tr>
+
 <tr> <td> MySQL database</td> <td>MYSQL_README</td> <td> Postfix
 1.0 </td> </tr>
 
diff --git a/postfix/proto/MONGODB_README.html b/postfix/proto/MONGODB_README.html
new file mode 100644 (file)
index 0000000..c660fc1
--- /dev/null
@@ -0,0 +1,232 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<title>Postfix MongoDB Howto</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+</head>
+<body>
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix MongoDB Howto</h1>
+<hr>
+
+<h2>MongoDB Support in Postfix</h2>
+
+<p> Postfix can use MongoDB as a source for any of its lookups:
+aliases(5), virtual(5), canonical(5), etc. This allows you to keep
+information for your mail service in a replicated noSQL database
+with fine-grained access controls. By not storing it locally on the
+mail server, the administrators can maintain it from anywhere, and
+the users can control whatever bits of it you think appropriate.
+You can have multiple mail servers using the same information,
+without the hassle and delay of having to copy it to each. </p>
+
+<p> Topics covered in this document:</p>
+
+<ul>
+<li><a href="#build">Building Postfix with MongoDB support</a>
+<li><a href="#config">Configuring MongoDB lookups</a>
+<li><a href="#example_virtual">Example: virtual alias maps</a>
+<li><a href="#example_mailing_list">Example: Mailing lists</a>
+<li><a href="#example_projections">Example: MongoDB projections</a>
+<li><a href="#feedback">Feedback</a>
+</ul>
+
+<h2><a name="build">Building Postfix with MongoDB support</a></h2>
+
+<p>These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p>The Postfix MongoDB client requires the mongo-c-driver library.
+This can be built from source code from <a
+href="https://github.com/mongodb/mongo-c-driver/releases">the
+mongod-c project</a>, or as a binary package from your OS distribution,
+typically named <b>mongo-c-driver-devel</b> or <b>libmongoc-dev</b>.
+Installing the mongo-c-driver library may also install <b>libbson</b>
+as a dependency. </p>
+
+<p> To build Postfix with mongodb map support, add to the CCARGS
+environment variable the options -DHAS_MONGODB and -I for the
+directory containing the mongodb headers, and specify the AUXLIBS_MONGODB
+with the libmongoc and libbson libraries, for example:</p>
+
+<blockquote>
+<pre>
+% make tidy
+% make -f Makefile.init makefiles \
+    CCARGS="$CCARGS -DHAS_MONGODB -I/usr/include/libmongoc-1.0 \
+    -I/usr/include/libbson-1.0" \
+    AUXLIBS_MONGODB="-lmongoc-1.0 -lbson-1.0"
+</pre>
+</blockquote>
+
+<p>The 'make tidy' command is needed only if you have previously
+built Postfix without MongoDB support. </p>
+
+<p>If your MongoDB shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option
+after "-lbson-1.0". Then, just run 'make'.</p>
+
+<h2><a name="config">Configuring MongoDB lookups</a></h2>
+
+<p> In order to use MongoDB lookups, define a MongoDB source as a
+table lookup in main.cf, for example: </p>
+
+<blockquote>
+<pre>
+alias_maps = hash:/etc/aliases, proxy:mongodb:/etc/postfix/mongo-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/mongo-aliases.cf can specify a number of
+parameters. For a complete description, see the mongodb_table(5)
+manual page. </p>
+
+<h2><a name="example_virtual">Example: virtual(5) alias maps</a></h2>
+
+<p> Here's a basic example for using MongoDB to look up virtual(5)
+aliases. Assume that in main.cf, you have: </p>
+
+<blockquote> 
+<pre>
+virtual_alias_maps = hash:/etc/postfix/virtual_aliases, 
+    proxy:mongodb:/etc/postfix/mongo-virtual-aliases.cf
+</pre>
+</blockquote> 
+
+<p> and in mongodb:/etc/postfix/mongo-virtual-aliases.cf you have: </p>
+
+<blockquote> 
+<pre>
+uri = mongodb+srv://user_name:password@some_server
+dbname = mail
+collection = mailbox
+query_filter = {"$or": [{"username":"%s"}, {"alias.address": "%s"}], "active": 1}
+result_attribute = username
+</pre>
+</blockquote> 
+
+<p>This example assumes mailbox names are stored in a MongoDB backend,
+in a format like:</p>
+
+<blockquote> 
+<pre>
+{ "username": "user@example.com",
+  "alias": [
+    {"address": "admin@example.com"},
+    {"address": "abuse@example.com"}
+  ],
+  "active": 1
+}
+</pre>
+</blockquote> 
+
+<p>Upon receiving mail for "admin@example.com" that isn't found in the
+/etc/postfix/virtual_aliases database, Postfix will search the
+MongoDB server/cluster listening at port 27017 on some_server. It
+will connect using the provided credentials, and search for any
+entries whose username is, or alias field has "admin@example.com".
+It will return the username attribute of those found, and build a
+list of their email addresses. </p>
+
+<h2><a name="example_mailing_list">Example: Mailing lists</a></h2>
+
+<p>When it comes to mailing lists, one way of implementing one would
+be as below:</p>
+
+<blockquote> 
+<pre>
+{ "name": "dev@example.com", "active": 1, "address": 
+  [ "hamid@example.com", "wietse@example.com", "viktor@example.com" ] }
+</pre>
+</blockquote> 
+
+<p>using the filter below, will result in a comma separated string
+with all email addresses in this list. </p>
+
+<blockquote> 
+<pre>
+query_filter = {"name": "%s", "active": 1}
+result_attribute = address
+</pre>
+</blockquote> 
+
+<p> Notes: </p>
+
+<ul>
+
+<li><p> As with <b>projection</b>, the Postfix mongodb client
+automatically removes the top-level '_id' field from a result. </p>
+</li>
+
+<li><p> The Postfix mongodb client will only parse result fields
+with data types UTF8, INT32, INT64 and ARRAY. Other fields will be
+ignored, with a warning in the logs. </p> </li>
+
+</ul>
+
+<h2><a name="example_projections">Example: advanced projections</a></h2>
+
+<p>This module also supports the use of more complex MongoDB
+projections.  There may be some use cases where operations such as
+concatenation are necessary to be performed on the data retrieved
+from the database. Although it is encouraged to keep the database
+design simple enough so this is not necessary, postfix supports the
+use of MongoDB projections to achieve the goal. </p>
+
+<p>Consider the example below:</p>
+
+<blockquote> 
+<pre>
+{ "username": "user@example.com",
+  "local_part": "user",
+  "domain": "example.com",
+  "alias": [
+    {"address": "admin@example.com"},
+    {"address": "abuse@example.com"}
+  ],
+  "active": 1
+}
+</pre>
+</blockquote> 
+
+<p>virtual_mailbox_maps can be created using below parameters in a
+mongodb:/etc/postfix/mongo-virtual-mailboxes.cf file:</p>
+
+<blockquote> 
+<pre>
+uri = mongodb+srv://user_name:password@some_server
+dbname = mail
+collection = mailbox
+query_filter = {"$or": [{"username":"%s"}, {"alias.address": "%s"}], "active": 1}
+projection = { "mail_path": {"$concat": ["$domain", "/", "$local_part"]} }
+</pre>
+</blockquote> 
+
+<p>This will return 'example.com/user' path built from the database fields. </p>
+
+<p>A couple of considerations when using projections:</p>
+
+<ul>
+
+<li><p>As with <b>result_attribute</b>, the Postfix mongodb client
+automatically removes the top-level '_id' field from a projection
+result. </p></li>
+
+<li><p> The Postfix mongodb client will only parse fields with data
+types UTF8, INT32, INT64 and ARRAY. Other fields will be ignored,
+with a warning in the logs. It is suggested to exclude any unnecessary
+fields when using a projection. </p></li>
+
+</ul>
+<h2><a name="feedback">Feedback</a></h2>
+
+<p> If you have questions, send them to postfix-users@postfix.org.
+Please include relevant information about your Postfix setup:
+MongoDB-related output from postconf, which libraries you built
+with, and such. If your question involves your database contents,
+please include the applicable bits of some database entries. </p>
+
+</body>
+
+</html>
index 511bd4448b97774b199c2b1f267ba4c2ce2b1972..ad7f73e5008e26b56d0f320f69d252209e216ef0 100644 (file)
@@ -30,6 +30,7 @@ HTML  = ../html/ADDRESS_CLASS_README.html \
        ../html/LMDB_README.html \
        ../html/MEMCACHE_README.html \
        ../html/MILTER_README.html \
+       ../html/MONGODB_README.html \
        ../html/MULTI_INSTANCE_README.html \
        ../html/MYSQL_README.html ../html/NFS_README.html \
        ../html/OVERVIEW.html \
@@ -78,6 +79,7 @@ README        = ../README_FILES/ADDRESS_CLASS_README \
        ../README_FILES/LMDB_README \
        ../README_FILES/MEMCACHE_README \
        ../README_FILES/MILTER_README \
+       ../README_FILES/MONGODB_README \
        ../README_FILES/MULTI_INSTANCE_README \
        ../README_FILES/MYSQL_README ../README_FILES/NFS_README \
        ../README_FILES/OVERVIEW \
@@ -240,6 +242,9 @@ clobber:
 ../html/MILTER_README.html: MILTER_README.html
        $(DETAB) $? | $(POSTLINK) >$@
 
+../html/MONGODB_README.html: MONGODB_README.html
+       $(DETAB) $? | $(POSTLINK) >$@
+
 ../html/MULTI_INSTANCE_README.html: MULTI_INSTANCE_README.html
        $(DETAB) $? | $(POSTLINK) >$@
 
@@ -420,6 +425,9 @@ clobber:
 ../README_FILES/MILTER_README: MILTER_README.html
        $(DETAB) $? | $(HT2READ) >$@
 
+../README_FILES/MONGODB_README: MONGODB_README.html
+       $(DETAB) $? | $(HT2READ) >$@
+
 ../README_FILES/MULTI_INSTANCE_README: MULTI_INSTANCE_README.html
        $(DETAB) $? | $(HT2READ) >$@
 
diff --git a/postfix/proto/mongodb_table b/postfix/proto/mongodb_table
new file mode 100644 (file)
index 0000000..81dfc8e
--- /dev/null
@@ -0,0 +1,240 @@
+#++
+# NAME
+#      mongodb_table 5
+# SUMMARY
+#      Postfix MongoDB client configuration
+# SYNOPSIS
+#      \fBpostmap -q "\fIstring\fB" mongodb:/etc/postfix/\fIfilename\fR
+#
+#      \fBpostmap -q - mongodb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+#      The Postfix mail system uses optional tables for address
+#      rewriting or mail routing. These tables are usually in
+#      \fBdbm\fR or \fBdb\fR format.
+#
+#      Alternatively, lookup tables can be specified as MongoDB
+#      databases.  In order to use MongoDB lookups, define a MongoDB
+#      source as a lookup table in main.cf, for example:
+# .nf
+#          alias_maps = mongodb:/etc/postfix/mongodb-aliases.cf
+# .fi
+#
+#      In this example, the file /etc/postfix/mongodb-aliases.cf
+#      has the same format as the Postfix main.cf file, and can
+#      specify the parameters described below. It is also possible
+#      to have the configuration in main.cf; see "OBSOLETE MAIN.CF
+#      PARAMETERS" below.
+#
+#      It is strongly recommended to use proxy:mongodb, in order
+#      to reduce the number of database connections. For example:
+# .nf
+#          alias_maps = proxy:mongodb:/etc/postfix/mongodb-aliases.cf
+# .fi
+#
+#      Note: when using proxy:mongodb:/\fIfile\fR, the file must
+#      be readable by the unprivileged postfix user (specified
+#      with the Postfix mail_owner configuration parameter).
+# MONGODB PARAMETERS
+# .ad
+# .fi
+# .IP "\fBuri\fR"
+#      The URI of mongo server/cluster that Postfix will try to
+#      connect to and query from. Please see
+# .nf
+#      https://www.mongodb.com/docs/manual/reference/connection-string/
+# .fi
+#
+#      Example:
+# .nf
+#          uri = mongodb+srv://user:pass@loclhost:27017/mail
+# .fi
+# .IP "\fBdbname\fR"
+#      Name of the database to read the information from.
+#      Example:
+# .nf
+#          dbname = mail
+# .fi
+# .IP "\fBcollection\fR"
+#      Name of the collection (table) to read the information from.
+#      Example:
+# .nf
+#          collection = mailbox
+# .fi
+# .IP "\fBquery_filter\fR"
+#      The MongoDB query template used to search the database,
+#      where \fB%s\fR is a substitute for the email address that
+#      Postfix is trying to resolve. Please see:
+# .nf
+#      https://www.mongodb.com/docs/manual/tutorial/query-documents/
+# .fi
+#
+#      Example:
+# .nf
+#          query_filter = {"$or": [{"username": "%s"}, {"alias.address": "%s"}], "active": 1}
+# .fi
+#
+#      This parameter supports the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+#      This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+#      This is replaced by the input key. The %s must appear in
+#      quotes, because all Postfix queries are strings containing
+#      (parts from) a domain or email address. Postfix makes no
+#      numerical queries.
+# .IP "\fB%u\fR"
+#      When the input key is an address of the form user@domain,
+#      \fB%u\fR is replaced by the local part of the address.
+#      Otherwise, \fB%u\fR is replaced by the entire search string.
+# .IP "\fB%d\fR"
+#      When the input key is an address of the form user@domain,
+#      \fB%d\fR is replaced by the domain part of the address.
+# .IP "\fB%[1-9]\fR"
+#      The patterns %1, %2, ... %9 are replaced by the corresponding
+#      most significant component of the input key's domain. If
+#      the input key is \fIuser@mail.example.com\fR, then %1 is
+#      \fBcom\fR, %2 is \fBexample\fR and %3 is \fBmail\fR.
+# .RE
+# .IP
+#      In the above substitutions, characters will be quoted as
+#      required by RFC 4627. For example, each double quote or
+#      backslash character will be escaped with a backslash
+#      characacter.
+# .IP "\fBprojection\fR"
+#      Advanced MongoDB query projections. Please see:
+# .nf
+#      https://www.mongodb.com/docs/manual/tutorial/project-fields-from-query-results/
+# .fi
+#
+# .RS
+# .IP \(bu
+#      If \fBprojection\fR is non-empty, then \fBresult_attribute\fR
+#      must be empty.
+# .IP \(bu
+#      This implementation can extract information only from result
+#      fields that have type \fBstring\fR (UTF8), \fBinteger\fR
+#      (int32, int64) and \fBarray\fR. Other result fields will
+#      be ignored with a warning. Please see:
+# .nf
+#      https://mongoc.org/libbson/current/bson_type_t.html
+# .fi
+# .IP \(bu
+#      As with \fBresult_attribute\fR, the top-level _id field
+#      (type OID) is automatically removed from projection results.
+# .RE
+# .IP "\fBresult_attribute\fR"
+#      Comma or whitespace separated list with the names of fields
+#      to be returned in a lookup result.
+#
+# .RS
+# .IP \(bu
+#      If \fBresult_attribute\fR is non-empty, then \fBprojection\fR
+#      must be empty.
+# .IP \(bu
+#      As with \fBprojection\fR, the top-level _id field (type
+#      OID) is automatically removed from lookup results.
+# .RE
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+#      Format template applied to the result from \fBprojection\fR
+#      or \fBresult_attribute\fR. Most commonly used to append (or
+#      prepend) text to the result. This parameter supports the
+#      following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+#      This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+#      This is replaced by the value of the result attribute. When
+#      result is empty it is skipped.
+# .IP "\fB%u\fR
+#      When the result attribute value is an address of the form
+#      user@domain, \fB%u\fR is replaced by the local part of the
+#      address. When the result has an empty localpart it is
+#      skipped.
+# .IP "\fB%d\fR"
+#      When a result attribute value is an address of the form
+#      user@domain, \fB%d\fR is replaced by the domain part of the
+#      attribute value. When the result is unqualified it is
+#      skipped.
+# .IP "\fB%[SUD1-9]\fR"
+#      The upper-case and decimal digit expansions interpolate the
+#      parts of the input key rather than the result. Their behavior
+#      is identical to that described with \fBquery_filter\fR, and
+#      in fact because the input key is known in advance, lookups
+#      whose key does not contain all the information specified
+#      in the result template are suppressed and return no results.
+# .RE
+# .IP
+#      For example, using "result_format = smtp:[%s]" allows one
+#      to use a mailHost attribute as the basis of a transport(5)
+#      table. After applying the result format, multiple values
+#      are concatenated as comma separated strings. The expansion_limit
+#      parameter explained below allows one to restrict the number
+#      of values in the result, which is especially useful for
+#      maps that should return a single value.
+#
+#      The default value \fB%s\fR specifies that each
+#      attribute value should be used as is.
+#
+#      NOTE: DO NOT put quotes around the result format! The result
+#      is not a JSON string.
+# .IP "\fBdomain (default: no domain list)\fR"
+#      This is a list of domain names, paths to files, or "type:table"
+#      databases. When specified, only fully qualified search keys
+#      with a *non-empty* localpart and a matching domain are
+#      eligible for lookup: 'user' lookups, bare domain lookups
+#      and "@domain" lookups are not performed. This can significantly
+#      reduce the query load on the backend database. Example:
+# .nf
+#          domain = postfix.org, hash:/etc/postfix/searchdomains
+# .fi
+# .IP "\fBexpansion_limit (default: 0)\fR"
+#      A limit on the total number of result elements returned (as
+#      a comma separated list) by a lookup against the map.  A
+#      setting of zero disables the limit. Lookups fail with a
+#      temporary error if the limit is exceeded. Setting the limit
+#      to 1 ensures that lookups do not return multiple values.
+# OBSOLETE MAIN.CF PARAMETERS
+# .ad
+# .fi
+#      MongoDB parameters can also be defined in main.cf. Specify
+#      as MongoDB source a name that doesn't begin with a slash
+#      or a dot. The MongoDB parameters will then be accessible
+#      as the name you've given the source in its definition, an
+#      underscore, and the name of the parameter. For example, if
+#      a map is specified as "mongodb:\fImongodb_source\fR", the
+#      "uri" parameter would be defined in main.cf as
+#      "\fImongodb_source\fR_uri".
+#
+#      Note: with this form, passwords are written in main.cf,
+#      which is normally world-readable, and '$' in a mongodb
+#      parameter setting needs to be written as '$$'.
+# SEE ALSO
+#      postmap(1), Postfix lookup table maintenance
+#      postconf(5), configuration parameters
+# README FILES
+# .ad
+# .fi
+#      Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+#      html_directory\fR" to locate this information.
+# .na
+# .nf
+#      DATABASE_README, Postfix lookup table overview
+#      MONGODB_README, Postfix MONGODB client guide
+# LICENSE
+# .ad
+# .fi
+#      The Secure Mailer license must be distributed with this software.
+# HISTORY
+#      MongoDB support was introduced with Postfix version 3.9.
+# AUTHOR(S)
+#      Hamid Maadani (hamid@dexo.tech)
+#      Dextrous Technologies, LLC
+#
+#      Edited by:
+#      Wietse Venema
+#      porcupine.org
+#
+#      Based on prior work by:
+#      Stephan Ferraro
+#      Aionda GmbH
+#--
index c9b2602414f2d401a0af062706e33c92f61947c4..37436858b4eff73c2357663e62b0505434712c8e 100644 (file)
@@ -1595,3 +1595,4 @@ EOD
 chunking
 allowlists
 FWS
+mongodb
index e1df531f74146e130f366597efc6e96b1ceb7c22..d79c684ee2c6e39dc84be81c7971a90d3dc78678 100644 (file)
@@ -355,3 +355,5 @@ RFC 2045 Sections 2 7 and 2 8 br br Such clients can be
 to become a list of comma separated names br br This feature
 the form of a domain name hostname hostname service hostname service 
 expected to become a list of comma separated names br br This
+Postfix  Postfix can use MongoDB as a source for any of its lookups aliases 5 virtual 5 canonical 5 etc This allows you to keep information for your mail service in a replicated noSQL database with fine grained access controls By not storing it
+ CCARGS CCARGS DHAS_MONGODB I usr include libmongoc 1 0 
index 9aa64c6e9ce0b0b80d36b8af1c3ebb0c7e44fdd9..41d79beed19f720f6beb1775a52d55c75c754a45 100644 (file)
@@ -1814,3 +1814,23 @@ inlined
 stringz
 Sarvepalli
 uXXXX
+Aionda
+Ferraro
+GmbH
+Hamid
+LLC
+Maadani
+MongoDB
+PRId
+bson
+dexo
+hamid
+itoa
+libmongoc
+mongdb
+mongo
+mongodb
+mongodbconf
+Dextrous
+Mongo
+SUD
index 16d0e729f08ea6e947ec7572d74378e00bdd6568..d5e53d3a83d3d9ace03599d69af1ba27e4ed7323 100644 (file)
@@ -70,3 +70,6 @@ dehtml
 NONPROD
 LC
 Philosof
+MONGODB
+Refactored
+Vijay
index 6baa193e538493c7c15bc8c55765056c5ca37cb8..79c1ce5333fc93b4889ecfdcc892071d8bbf1f1a 100644 (file)
@@ -359,3 +359,18 @@ wraptls
 api
 MinProtocol
 spammy
+concat
+hamid
+ina
+lbson
+libbson
+libmobgo
+libmongoc
+lmongoc
+mongo
+mongod
+noSQL
+srv
+viktor
+MONGODB
+MongoDB
index 562290232e431141203e5a0b4933839c8a94f521..c7a1d36af7e565c03530fd3e29c58da424e35dad 100644 (file)
@@ -3,7 +3,7 @@ SRCS    = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \
        canon_addr.c cfg_parser.c cleanup_strerror.c cleanup_strflags.c \
        clnt_stream.c conv_time.c db_common.c debug_peer.c debug_process.c \
        defer.c deliver_completed.c deliver_flock.c deliver_pass.c \
-       deliver_request.c dict_ldap.c dict_mysql.c dict_pgsql.c \
+       deliver_request.c dict_ldap.c dict_mongodb.c dict_mysql.c dict_pgsql.c \
        dict_proxy.c dict_sqlite.c domain_list.c dot_lockfile.c dot_lockfile_as.c \
        dsb_scan.c dsn.c dsn_buf.c dsn_mask.c dsn_print.c dsn_util.c \
        ehlo_mask.c ext_prop.c file_id.c flush_clnt.c header_opts.c \
@@ -80,13 +80,13 @@ OBJS        = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
 # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
 # When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
 # otherwise it sets the PLUGIN_* macros.
-MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o
+MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o dict_mongodb.o
 
 HDRS   = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \
        canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \
        conv_time.h db_common.h debug_peer.h debug_process.h defer.h \
        deliver_completed.h deliver_flock.h deliver_pass.h deliver_request.h \
-       dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h dict_sqlite.h domain_list.h \
+       dict_ldap.h dict_mysql.h dict_pgsql.h dict_mongodb.h dict_proxy.h dict_sqlite.h domain_list.h \
        dot_lockfile.h dot_lockfile_as.h dsb_scan.h dsn.h dsn_buf.h \
        dsn_mask.h dsn_print.h dsn_util.h ehlo_mask.h ext_prop.h \
        file_id.h flush_clnt.h header_opts.h header_token.h input_transp.h \
@@ -136,7 +136,7 @@ LIBS        = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
 LIB_DIR        = ../../lib
 INC_DIR        = ../../include
 PLUGIN_MAP_SO = $(LIB_PREFIX)ldap$(LIB_SUFFIX) $(LIB_PREFIX)mysql$(LIB_SUFFIX) \
-       $(LIB_PREFIX)pgsql$(LIB_SUFFIX) $(LIB_PREFIX)sqlite$(LIB_SUFFIX)
+       $(LIB_PREFIX)pgsql$(LIB_SUFFIX) $(LIB_PREFIX)sqlite$(LIB_SUFFIX) $(LIB_PREFIX)mongodb$(LIB_SUFFIX)
 MAKES  =
 
 .c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
@@ -173,6 +173,9 @@ $(LIB_PREFIX)pgsql$(LIB_SUFFIX): dict_pgsql.o
 $(LIB_PREFIX)sqlite$(LIB_SUFFIX): dict_sqlite.o
        $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_sqlite.o $(AUXLIBS_SQLITE)
 
+$(LIB_PREFIX)mongodb$(LIB_SUFFIX): dict_mongodb.o
+       $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_mongodb.o $(AUXLIBS_MONGODB)
+
 update: $(LIB_DIR)/$(LIB) $(HDRS) $(PLUGIN_MAP_SO_UPDATE)
        -for i in $(HDRS); \
        do \
@@ -1210,6 +1213,24 @@ dict_memcache.o: dict_memcache.c
 dict_memcache.o: dict_memcache.h
 dict_memcache.o: memcache_proto.h
 dict_memcache.o: string_list.h
+dict_mongodb.o: ../../include/argv.h
+dict_mongodb.o: ../../include/auto_clnt.h
+dict_mongodb.o: ../../include/check_arg.h
+dict_mongodb.o: ../../include/dict.h
+dict_mongodb.o: ../../include/match_list.h
+dict_mongodb.o: ../../include/msg.h
+dict_mongodb.o: ../../include/myflock.h
+dict_mongodb.o: ../../include/mymalloc.h
+dict_mongodb.o: ../../include/stringops.h
+dict_mongodb.o: ../../include/sys_defs.h
+dict_mongodb.o: ../../include/vbuf.h
+dict_mongodb.o: ../../include/vstream.h
+dict_mongodb.o: ../../include/vstring.h
+dict_mongodb.o: cfg_parser.h
+dict_mongodb.o: db_common.h
+dict_mongodb.o: dict_mongodb.c
+dict_mongodb.o: dict_mongodb.h
+dict_mongodb.o: string_list.h
 dict_mysql.o: ../../include/argv.h
 dict_mysql.o: ../../include/check_arg.h
 dict_mysql.o: ../../include/dict.h
@@ -1861,6 +1882,7 @@ mail_dict.o: ../../include/vstream.h
 mail_dict.o: ../../include/vstring.h
 mail_dict.o: dict_ldap.h
 mail_dict.o: dict_memcache.h
+mail_dict.o: dict_mongodb.h
 mail_dict.o: dict_mysql.h
 mail_dict.o: dict_pgsql.h
 mail_dict.o: dict_proxy.h
diff --git a/postfix/src/global/dict_mongodb.c b/postfix/src/global/dict_mongodb.c
new file mode 100644 (file)
index 0000000..1aa36b3
--- /dev/null
@@ -0,0 +1,569 @@
+/*++
+/* NAME
+/*     dict_mongodb 3
+/* SUMMARY
+/*     dictionary interface to mongodb, compatible with libmongoc-1.0
+/* SYNOPSIS
+/*     #include <dict_mongodb.h>
+/*
+/*     DICT *dict_mongodb_open(name, open_flags, dict_flags)
+/*     const char *name;
+/*     int     open_flags;
+/*     int     dict_flags;
+/* DESCRIPTION
+/*     dict_mongodb_open() opens a MongoDB database, providing a
+/*     dictionary interface for Postfix mappings. The result is a
+/*     pointer to the installed dictionary.
+/*
+/*     Configuration parameters are described in mongodb_table(5).
+/*
+/*     Arguments:
+/* .IP name
+/*     Either the path to the MongoDB configuration file (if it
+/*     starts with '/' or '.'), or the prefix which will be used
+/*     to obtain main.cf configuration parameters for this search.
+/*
+/*     In the first case, configuration parameters are specified
+/*     in the file as \fIname\fR=\fIvalue\fR pairs.
+/*
+/*     In the second case, the configuration parameters are prefixed
+/*     with the value of \fIname\fR and an underscore, and they
+/*     are specified in main.cf. For example, if this value is
+/*     \fImongodbconf\fR, the parameters would look like
+/*     \fImongodbconf_uri\fR, \fImongodbconf_collection\fR, and
+/*     so on.
+/* .IP open_flags
+/*     Must be O_RDONLY
+/* .IP dict_flags
+/*     See dict_open(3).
+/* SEE ALSO
+/*     dict(3) generic dictionary manager
+/* HISTORY
+/* .ad
+/* .fi
+/*     MongoDB support was added in Postfix 3.9.
+/* AUTHOR(S)
+/*     Hamid Maadani (hamid@dexo.tech)
+/*     Dextrous Technologies, LLC
+/*
+/*     Edited by:
+/*     Wietse Venema
+/*     porcupine.org
+/*
+/*     Based on prior work by:
+/*     Stephan Ferraro
+/*     Aionda GmbH
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#ifdef HAS_MONGODB
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>                  /* C99 PRId64 */
+
+#include <bson/bson.h>
+#include <mongoc/mongoc.h>
+
+ /*
+  * Utility library.
+  */
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <auto_clnt.h>
+#include <vstream.h>
+
+ /*
+  * Global library.
+  */
+#include <cfg_parser.h>
+#include <db_common.h>
+
+ /*
+  * Application-specific.
+  */
+#include <dict_mongodb.h>
+
+ /*
+  * Initial size for dynamically-allocated buffers.
+  */
+#ifndef BUFFER_SIZE
+#define BUFFER_SIZE 1024
+#endif
+
+#define INIT_VSTR(buf, len) do { \
+       if (buf == 0) \
+               buf = vstring_alloc(len); \
+       VSTRING_RESET(buf); \
+       VSTRING_TERMINATE(buf); \
+    } while (0)
+
+/* Structure of one mongodb dictionary handle. */
+typedef struct {
+    /* Initialized by dict_mongodb_open(). */
+    DICT    dict;                      /* Parent class */
+    CFG_PARSER *parser;                        /* Configuration file parser */
+    mongoc_client_t *client;           /* Mongo C client handle */
+    /* Initialized by mongodb_parse_config(). */
+    char   *uri;                       /* mongodb+srv:/*localhost:27017 */
+    char   *dbname;                    /* Database name */
+    char   *collection;                        /* Collection name */
+    char   *query_filter;              /* db_common_expand() query template */
+    char   *projection;                        /* Advanced MongoDB projection */
+    char   *result_attribute;          /* The key(s) to return the data for */
+    char   *result_format;             /* db_common_expand() result_template */
+    int     expansion_limit;           /* Result expansion limit */
+    void   *ctx;                       /* db_common handle */
+} DICT_MONGODB;
+
+/* Per-process initialization. */
+static bool init_done = false;
+
+/* itoa - int64_t to string */
+
+static char *itoa(int64_t val)
+{
+    static char buf[21] = {0};
+    int     ret;
+
+    /*
+     * XXX(Wietse) replaced custom code with standard library calls that
+     * handle zero, and negative values.
+     */
+#define PRId64_FORMAT "%" PRId64
+
+    ret = snprintf(buf, sizeof(buf), PRId64_FORMAT, val);
+    if (ret < 0)
+       msg_panic("itoa: output error for '%s'", PRId64_FORMAT);
+    if (ret >= sizeof(buf))
+       msg_panic("itoa: output for '%s' exceeds space %ld",
+                 PRId64_FORMAT, sizeof(buf));
+    return (buf);
+}
+
+/* mongodb_parse_config - parse mongodb configuration file */
+
+static void mongodb_parse_config(DICT_MONGODB *dict_mongodb,
+                                        const char *mongodbcf)
+{
+    CFG_PARSER *p = dict_mongodb->parser;
+
+    /*
+     * Parse the configuration file.
+     */
+    dict_mongodb->uri = cfg_get_str(p, "uri", NULL, 1, 0);
+    dict_mongodb->dbname = cfg_get_str(p, "dbname", NULL, 1, 0);
+    dict_mongodb->collection = cfg_get_str(p, "collection", NULL, 1, 0);
+    dict_mongodb->query_filter = cfg_get_str(p, "query_filter", NULL, 1, 0);
+
+    /*
+     * One of projection and result_attribute must be specified. That is
+     * enforced in the caller.
+     */
+    dict_mongodb->projection = cfg_get_str(p, "projection", NULL, 0, 0);
+    dict_mongodb->result_attribute
+       = cfg_get_str(p, "result_attribute", NULL, 0, 0);
+    dict_mongodb->result_format
+       = cfg_get_str(dict_mongodb->parser, "result_format", "%s", 1, 0);
+    dict_mongodb->expansion_limit
+       = cfg_get_int(dict_mongodb->parser, "expansion_limit", 10, 0, 100);
+
+    /*
+     * db_common query parsing and domain pattern lookup.
+     */
+    dict_mongodb->ctx = 0;
+    (void) db_common_parse(&dict_mongodb->dict, &dict_mongodb->ctx,
+                          dict_mongodb->query_filter, 1);
+    db_common_parse_domain(dict_mongodb->parser, dict_mongodb->ctx);
+}
+
+/* expand_value - expand lookup result value */
+
+static bool expand_value(DICT_MONGODB *dict_mongodb, const char *p,
+                                const char *lookup_name,
+                                VSTRING *resultString,
+                                int *expansion, const char *key)
+{
+
+    /*
+     * If a lookup result cannot be processed due to an expansion limit
+     * error, return a DICT_ERR_RETRY error code and a 'false' result value.
+     * As documented for many dict_xxx() implementations, and expansion limit
+     * error is considered a temporary error.
+     */
+    if (dict_mongodb->expansion_limit > 0
+       && ++(*expansion) > dict_mongodb->expansion_limit) {
+       msg_warn("%s:%s: expansion limit exceeded for key: '%s'",
+                dict_mongodb->dict.type, dict_mongodb->dict.name, key);
+       dict_mongodb->dict.error = DICT_ERR_RETRY;
+       return (false);
+    }
+
+    /*
+     * XXX(Wietse) Added the dict_mongodb_lookup() lookup_name argument,
+     * because it selects code paths inside db_common_expand() that are
+     * specifically for lookup results instead of lookup keys, including
+     * %[SUD] substitution.
+     */
+    db_common_expand(dict_mongodb->ctx, dict_mongodb->result_format, p,
+                    lookup_name, resultString, 0);
+    return (true);
+}
+
+/* get_result_string - convert lookup result to string, or set dict.error */
+
+static char *get_result_string(DICT_MONGODB *dict_mongodb,
+                                      VSTRING *resultString,
+                                      bson_iter_t *iter,
+                                      const char *lookup_name,
+                                      int *expansion,
+                                      const char *key)
+{
+    char   *p = NULL;
+    bool    got_one_result = false;
+
+    /*
+     * If a lookup result cannot be processed due to an error, return a
+     * non-zero error code and a NULL result value.
+     */
+    INIT_VSTR(resultString, BUFFER_SIZE);
+    while (dict_mongodb->dict.error == DICT_ERR_NONE && bson_iter_next(iter)) {
+       switch (bson_iter_type(iter)) {
+       case BSON_TYPE_UTF8:
+           p = (char *) bson_iter_utf8(iter, NULL);
+           if (!bson_utf8_validate(p, strlen(p), true)) {
+               msg_warn("%s:%s: invalid UTF-8 in lookup result '%s'",
+                      dict_mongodb->dict.type, dict_mongodb->dict.name, p);
+               dict_mongodb->dict.error = DICT_ERR_RETRY;
+               break;
+           }
+           got_one_result |= expand_value(dict_mongodb, p, lookup_name,
+                                          resultString, expansion, key);
+           break;
+       case BSON_TYPE_INT64:
+       case BSON_TYPE_INT32:
+           p = itoa(bson_iter_as_int64(iter));
+           got_one_result |= expand_value(dict_mongodb, p, lookup_name,
+                                          resultString, expansion, key);
+           break;
+       case BSON_TYPE_ARRAY:
+           const uint8_t *dataBuffer = NULL;
+           unsigned int len = 0;
+           bson_iter_t dataIter;
+           bson_t *data = NULL;
+
+           /*
+            * XXX(Wietse) are there any non-error cases, such as a valid but
+            * empty array, where bson_new_from_data() or bson_iter_init()
+            * would return null or false? If there are no such cases then we
+            * must handle null/false as an error.
+            */
+           bson_iter_array(iter, &len, &dataBuffer);
+           if ((data = bson_new_from_data(dataBuffer, len)) != 0
+               && bson_iter_init(&dataIter, data)) {
+               VSTRING *iterResult = vstring_alloc(BUFFER_SIZE);
+
+               if ((p = get_result_string(dict_mongodb, iterResult, &dataIter,
+                                      lookup_name, expansion, key)) != 0) {
+                   vstring_sprintf_append(resultString, (got_one_result) ?
+                                          ",%s" : "%s", p);
+                   got_one_result |= true;
+               }
+               vstring_free(iterResult);
+           }
+           bson_destroy(data);
+           break;
+       default:
+           /* Unexpected field type. As documented, warn and ignore. */
+           msg_warn("%s:%s: failed to retrieve value of '%s', "
+                    "Unknown result type %d.", dict_mongodb->dict.type,
+                    dict_mongodb->dict.name, bson_iter_key(iter),
+                    bson_iter_type(iter));
+           break;
+       }
+    }
+    if (dict_mongodb->dict.error != DICT_ERR_NONE || !got_one_result)
+       return (0);
+    return (vstring_str(resultString));
+}
+
+/* dict_mongdb_quote - quote json string */
+
+static void dict_mongdb_quote(DICT *dict, const char *name, VSTRING *result)
+{
+    /* quote_for_json_append() will resize the result buffer as needed. */
+    (void) quote_for_json_append(result, name, -1);
+}
+
+/* dict_mongdb_append_result_attributes - projection builder */
+
+static int dict_mongdb_append_result_attribute(bson_t * projection,
+                                              const char *result_attribute)
+{
+    char   *ra = mystrdup(result_attribute);
+    char   *pp = ra;
+    char   *cp;
+    int     ok = 1;
+
+    while (ok && (cp = mystrtok(&pp, CHARS_COMMA_SP)) != 0)
+       ok = BSON_APPEND_INT32(projection, cp, 1);
+    myfree(ra);
+    return (ok);
+}
+
+/* dict_mongodb_lookup - find database entry using mongo query language */
+
+static const char *dict_mongodb_lookup(DICT *dict, const char *name)
+{
+    DICT_MONGODB *dict_mongodb = (DICT_MONGODB *) dict;
+    mongoc_collection_t *coll = NULL;
+    mongoc_cursor_t *cursor = NULL;
+    bson_iter_t iter;
+    const bson_t *doc = NULL;
+    bson_t *query = NULL;
+    bson_t *options = NULL;
+    bson_t *projection = NULL;
+    bson_error_t error;
+    char   *result = NULL;
+    static VSTRING *queryString = NULL;
+    static VSTRING *resultString = NULL;
+    int     domain_rc;
+    int     expansion = 0;
+
+    dict_mongodb->dict.error = DICT_ERR_NONE;
+
+    /*
+     * If they specified a domain list for this map, then only search for
+     * addresses in domains on the list. This can significantly reduce the
+     * load on the database.
+     */
+    if ((domain_rc = db_common_check_domain(dict_mongodb->ctx, name)) == 0) {
+       if (msg_verbose)
+           msg_info("%s:%s: skipping lookup of '%s': domain mismatch",
+                    dict_mongodb->dict.type, dict_mongodb->dict.name, name);
+       return (0);
+    } else if (domain_rc < 0) {
+       DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
+    }
+
+    /*
+     * Ugly macros to make error and non-error handling code more readable.
+     * If code size is a concern, them an optimizing compiler can eliminate
+     * dead code or duplicated code.
+     */
+
+    /* Set an error code, and return null. */
+#define DICT_MONGODB_LOOKUP_ERR_RETURN(err) do { \
+       dict_mongodb->dict.error = (err); \
+       DICT_MONGODB_LOOKUP_RETURN((char *) 0); \
+} while (0);
+
+    /* Pass through any error, and return the specified value. */
+#define DICT_MONGODB_LOOKUP_RETURN(val) do { \
+       if (coll) mongoc_collection_destroy(coll); \
+       if (cursor) mongoc_cursor_destroy(cursor); \
+       if (query) bson_destroy(query); \
+       if (options) bson_destroy(options); \
+       if (projection) bson_destroy(projection); \
+       return (val); \
+    } while (0)
+
+    coll = mongoc_client_get_collection(dict_mongodb->client,
+                                       dict_mongodb->dbname,
+                                       dict_mongodb->collection);
+    if (!coll) {
+       msg_warn("%s:%s: failed to get collection [%s] from [%s]",
+                dict_mongodb->dict.type, dict_mongodb->dict.name,
+                dict_mongodb->collection, dict_mongodb->dbname);
+       DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+    }
+
+    /*
+     * Use the specified result projection, or craft one from the
+     * result_attribute. Exclude the _id field from the result.
+     */
+    options = bson_new();
+    if (dict_mongodb->projection) {
+       projection = bson_new_from_json((uint8_t *) dict_mongodb->projection,
+                                       -1, &error);
+       if (!projection) {
+           msg_warn("%s:%s: failed to create a projection from '%s': %s",
+                    dict_mongodb->dict.type, dict_mongodb->dict.name,
+                    dict_mongodb->projection, error.message);
+           DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+       }
+       if (!BSON_APPEND_INT32(projection, "_id", 0)
+           || !BSON_APPEND_DOCUMENT(options, "projection", projection)) {
+           msg_warn("%s:%s: failed to append a projection from '%s'",
+                    dict_mongodb->dict.type, dict_mongodb->dict.name,
+                    dict_mongodb->projection);
+           DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+       }
+    } else if (dict_mongodb->result_attribute) {
+       bson_t  res_attr;
+
+       if (!BSON_APPEND_DOCUMENT_BEGIN(options, "projection", &res_attr)
+           || !BSON_APPEND_INT32(&res_attr, "_id", 0)
+           || !dict_mongdb_append_result_attribute(&res_attr,
+                                            dict_mongodb->result_attribute)
+           || !bson_append_document_end(options, &res_attr)) {
+           msg_warn("%s:%s: failed to append a projection from '%s'",
+                    dict_mongodb->dict.type, dict_mongodb->dict.name,
+                    dict_mongodb->result_attribute);
+           DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+       }
+    } else {
+       /* Can't happen. The configuration parser should reject this. */
+       msg_panic("%s:%s: empty 'projection' and 'result_attribute'",
+                 dict_mongodb->dict.type, dict_mongodb->dict.name);
+    }
+
+    /*
+     * Expand filter template. This uses a quoting function to prevent
+     * metacharacter injection with parts from a crafted email address.
+     */
+    INIT_VSTR(queryString, BUFFER_SIZE);
+    if (!db_common_expand(dict_mongodb->ctx, dict_mongodb->query_filter,
+                         name, 0, queryString, dict_mongdb_quote))
+       /* Suppress the actual lookup if the expansion is empty. */
+       DICT_MONGODB_LOOKUP_RETURN(0);
+
+    /* Create the query from the expanded query template. */
+    query = bson_new_from_json((uint8_t *) vstring_str(queryString),
+                              -1, &error);
+    if (!query) {
+       msg_warn("%s:%s: failed to create a query from '%s': %s",
+                dict_mongodb->dict.type, dict_mongodb->dict.name,
+                vstring_str(queryString), error.message);
+       DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+    }
+    /* Run the query. */
+    cursor = mongoc_collection_find_with_opts(coll, query, options, NULL);
+    if (mongoc_cursor_error(cursor, &error)) {
+       msg_warn("%s:%s: cursor error for '%s': %s",
+                dict_mongodb->dict.type, dict_mongodb->dict.name,
+                vstring_str(queryString), error.message);
+       DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+    }
+    /* Convert the lookup result to C string. */
+    INIT_VSTR(resultString, BUFFER_SIZE);
+    while (mongoc_cursor_next(cursor, &doc)) {
+       if (bson_iter_init(&iter, doc)) {
+           result = get_result_string(dict_mongodb, resultString, &iter,
+                                      name, &expansion, name);
+       }
+    }
+    DICT_MONGODB_LOOKUP_RETURN(result);
+}
+
+/* dict_mongodb_close - close MongoDB database */
+
+static void dict_mongodb_close(DICT *dict)
+{
+    DICT_MONGODB *dict_mongodb = (DICT_MONGODB *) dict;
+
+    cfg_parser_free(dict_mongodb->parser);
+    if (dict_mongodb->ctx) {
+       db_common_free_ctx(dict_mongodb->ctx);
+    }
+    myfree(dict_mongodb->uri);
+    myfree(dict_mongodb->dbname);
+    myfree(dict_mongodb->collection);
+    myfree(dict_mongodb->query_filter);
+
+    if (dict_mongodb->result_attribute) {
+       myfree(dict_mongodb->result_attribute);
+    }
+    if (dict_mongodb->result_format) {
+       myfree(dict_mongodb->result_format);
+    }
+    if (dict_mongodb->projection) {
+       myfree(dict_mongodb->projection);
+    }
+    if (dict_mongodb->client) {
+       mongoc_client_destroy(dict_mongodb->client);
+    }
+    dict_free(dict);
+}
+
+/* dict_mongodb_open - open MongoDB database connection */
+
+DICT   *dict_mongodb_open(const char *name, int open_flags, int dict_flags)
+{
+    DICT_MONGODB *dict_mongodb;
+    CFG_PARSER *parser;
+    mongoc_uri_t *uri = 0;
+    bson_error_t error;
+
+    /* Sanity checks. */
+    if (open_flags != O_RDONLY) {
+       return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
+                              "%s:%s: map requires O_RDONLY access mode",
+                              DICT_TYPE_MONGODB, name));
+    }
+    /* Open the configuration file. */
+    if ((parser = cfg_parser_alloc(name)) == 0) {
+       return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
+                              "open %s: %m", name));
+    }
+    /* Create the dictionary object. */
+    dict_mongodb = (DICT_MONGODB *) dict_alloc(DICT_TYPE_MONGODB, name,
+                                              sizeof(*dict_mongodb));
+    dict_mongodb->dict.lookup = dict_mongodb_lookup;
+    dict_mongodb->dict.close = dict_mongodb_close;
+    dict_mongodb->dict.flags = dict_flags;
+    dict_mongodb->parser = parser;
+    dict_mongodb->dict.owner = cfg_get_owner(dict_mongodb->parser);
+    dict_mongodb->client = NULL;
+
+    /* Parse config. */
+    mongodb_parse_config(dict_mongodb, name);
+    if (!dict_mongodb->projection == !dict_mongodb->result_attribute) {
+       dict_mongodb_close(&dict_mongodb->dict);
+       return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
+        "%s:%s: specify exactly one of 'projection' or 'result_attribute'",
+                              DICT_TYPE_MONGODB, name));
+    }
+    /* One-time initialization of libmongoc 's internals. */
+    if (!init_done) {
+       mongoc_init();
+       init_done = true;
+    }
+#define DICT_MONGODB_OPEN_ERR_RETURN(d) do { \
+       DICT   *_d = (d); \
+       if (uri) mongoc_uri_destroy(uri); \
+       dict_mongodb_close(&dict_mongodb->dict); \
+       return (_d); \
+    } while (0);
+
+    uri = mongoc_uri_new_with_error(dict_mongodb->uri, &error);
+    if (!uri)
+       DICT_MONGODB_OPEN_ERR_RETURN(dict_surrogate(DICT_TYPE_MONGODB, name,
+                                                   open_flags, dict_flags,
+                                     "%s:%s: failed to parse URI '%s': %s",
+                                                   DICT_TYPE_MONGODB, name,
+                                        dict_mongodb->uri, error.message));
+
+    dict_mongodb->client = mongoc_client_new_from_uri_with_error(uri, &error);
+    if (!dict_mongodb->client)
+       DICT_MONGODB_OPEN_ERR_RETURN(dict_surrogate(DICT_TYPE_MONGODB, name,
+                                                   open_flags, dict_flags,
+                             "%s:%s: failed to create client for '%s': %s",
+                                                   DICT_TYPE_MONGODB, name,
+                                                   dict_mongodb->uri,
+                                                   error.message));
+
+    mongoc_uri_destroy(uri);
+    mongoc_client_set_error_api(dict_mongodb->client, MONGOC_ERROR_API_VERSION_2);
+    return (DICT_DEBUG (&dict_mongodb->dict));
+}
+
+#endif
diff --git a/postfix/src/global/dict_mongodb.h b/postfix/src/global/dict_mongodb.h
new file mode 100755 (executable)
index 0000000..d5120cb
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef _DICT_MONGODB_INCLUDED_
+#define _DICT_MONGODB_INCLUDED_
+
+/*++
+/* NAME
+/*     dict_mongodb 3h
+/* SUMMARY
+/*     dictionary interface to mongodb databases
+/* SYNOPSIS
+/*     #include <dict_mongodb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <dict.h>
+
+ /*
+  * External interface.
+  */
+#define DICT_TYPE_MONGODB "mongodb"
+
+extern DICT *dict_mongodb_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Hamid Maadani (hamid@dexo.tech)
+/*     Dextrous Technologies, LLC
+/*
+/*     Edited by:
+/*     Wietse Venema
+/*     porcupine.org
+/*
+/*     Based on prior work by:
+/*     Stephan Ferraro
+/*     Aionda GmbH
+/*--*/
+
+#endif
index c640a807a3226eb5cba7237c30f64f24e7982548..55ac5dc2266906500da0e8a90dcfe3fa9c98a497 100644 (file)
@@ -52,6 +52,7 @@
 #include <dict_pgsql.h>
 #include <dict_sqlite.h>
 #include <dict_memcache.h>
+#include <dict_mongodb.h>
 #include <mail_dict.h>
 #include <mail_params.h>
 #include <mail_dict.h>
@@ -71,6 +72,9 @@ static const DICT_OPEN_INFO dict_open_info[] = {
 #ifdef HAS_SQLITE
     DICT_TYPE_SQLITE, dict_sqlite_open, 0,
 #endif
+#ifdef HAS_MONGODB
+    DICT_TYPE_MONGODB, dict_mongodb_open, 0,
+#endif
 #endif                                 /* !USE_DYNAMIC_MAPS */
     DICT_TYPE_MEMCACHE, dict_memcache_open, 0,
     0,
index 98c9ccb328f77dbec2fc88c5d0d567ed85987226..34b806ed02f3af5d98e055656e59b05a9a7cdab4 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      "20240206"
+#define MAIL_RELEASE_DATE      "20240208"
 #define MAIL_VERSION_NUMBER    "3.9"
 
 #ifdef SNAPSHOT
index efae3652a603e48949e0a6862d5eef44fe138bec..0aa29e8888db5886ffc9725bcd034cb9d9e796d8 100644 (file)
@@ -17,7 +17,7 @@ MAKES = bool_table.h bool_vars.h int_table.h int_vars.h str_table.h \
        nint_table.h nint_vars.h nbool_table.h nbool_vars.h long_table.h \
        long_vars.h str_fn_table.h str_fn_vars.h 
 DB_MAKES= pcf_ldap_suffixes.h pcf_memcache_suffixes.h pcf_mysql_suffixes.h \
-       pcf_pgsql_suffixes.h pcf_sqlite_suffixes.h
+       pcf_pgsql_suffixes.h pcf_sqlite_suffixes.h pcf_mongodb_suffixes.h
 TEST_TMP= main.cf master.cf test*.tmp
 DUMMIES        = makes_dummy # for "make -j"
 PROG   = postconf
@@ -79,6 +79,9 @@ pcf_ldap_suffixes.h: ../global/dict_ldap.c
 pcf_memcache_suffixes.h: ../global/dict_memcache.c 
        sh extract_cfg.sh -d ../global/dict_memcache.c > $@
 
+pcf_mongodb_suffixes.h: ../global/dict_mongodb.c 
+       sh extract_cfg.sh -d ../global/dict_mongodb.c > $@
+
 pcf_mysql_suffixes.h: ../global/dict_mysql.c 
        sh extract_cfg.sh -d -s ../global/dict_mysql.c > $@
 
@@ -1149,6 +1152,7 @@ postconf_dbms.o: ../../include/dict_pgsql.h
 postconf_dbms.o: ../../include/dict_proxy.h
 postconf_dbms.o: ../../include/dict_regexp.h
 postconf_dbms.o: ../../include/dict_sqlite.h
+postconf_dbms.o: ../../include/dict_mongodb.h
 postconf_dbms.o: ../../include/htable.h
 postconf_dbms.o: ../../include/mac_expand.h
 postconf_dbms.o: ../../include/mac_parse.h
@@ -1170,6 +1174,7 @@ postconf_dbms.o: pcf_memcache_suffixes.h
 postconf_dbms.o: pcf_mysql_suffixes.h
 postconf_dbms.o: pcf_pgsql_suffixes.h
 postconf_dbms.o: pcf_sqlite_suffixes.h
+postconf_dbms.o: pcf_mongodb_suffixes.h
 postconf_dbms.o: postconf.h
 postconf_dbms.o: postconf_dbms.c
 postconf_edit.o: ../../include/argv.h
index 4f9f87d97160c63cac095a5621785a03294d39d3..93bcecca561a295eea1712c3cc5f8b16853b79d6 100644 (file)
 /*     ldap_table(5), Postfix LDAP client
 /*     lmdb_table(5), Postfix LMDB database driver
 /*     memcache_table(5), Postfix memcache client
+/*     mongodb_table(5), Postfix MongoDB client
 /*     mysql_table(5), Postfix MYSQL client
 /*     nisplus_table(5), Postfix NIS+ client
 /*     pcre_table(5), Associate PCRE pattern with value
index db7940462ec4738afd018219b69e4e429673abdc..a2820dda9b22326f1c6f712f050c230036f07e6b 100644 (file)
 #define STR(x) vstring_str(x)
 #define LEN(x) VSTRING_LEN(x)
 
-/* json_quote - quote JSON string */
-
-static char *json_quote(VSTRING *result, const char *text)
-{
-    unsigned char *cp;
-    int     ch;
-
-    /*
-     * We use short escape sequences for common control characters. Note that
-     * RFC 4627 allows "/" (0x2F) to be sent without quoting. Differences
-     * with RFC 4627: we send DEL (0x7f) as \u007F; the result remains RFC
-     * 4627 complaint.
-     */
-    VSTRING_RESET(result);
-    for (cp = (unsigned char *) text; (ch = *cp) != 0; cp++) {
-       if (UNEXPECTED(ISCNTRL(ch))) {
-           switch (ch) {
-           case '\b':
-               VSTRING_ADDCH(result, '\\');
-               VSTRING_ADDCH(result, 'b');
-               break;
-           case '\f':
-               VSTRING_ADDCH(result, '\\');
-               VSTRING_ADDCH(result, 'f');
-               break;
-           case '\n':
-               VSTRING_ADDCH(result, '\\');
-               VSTRING_ADDCH(result, 'n');
-               break;
-           case '\r':
-               VSTRING_ADDCH(result, '\\');
-               VSTRING_ADDCH(result, 'r');
-               break;
-           case '\t':
-               VSTRING_ADDCH(result, '\\');
-               VSTRING_ADDCH(result, 't');
-               break;
-           default:
-               vstring_sprintf_append(result, "\\u%04X", ch);
-               break;
-           }
-       } else {
-           switch (ch) {
-           case '\\':
-           case '"':
-               VSTRING_ADDCH(result, '\\');
-               /* FALLTHROUGH */
-           default:
-               VSTRING_ADDCH(result, ch);
-               break;
-           }
-       }
-    }
-    VSTRING_TERMINATE(result);
-
-    /*
-     * Force the result to be UTF-8 (with SMTPUTF8 enabled) or ASCII (with
-     * SMTPUTF8 disabled).
-     */
-    printable(STR(result), '?');
-    return (STR(result));
-}
-
 /* json_message - report status for one message */
 
 static void format_json(VSTREAM *showq_stream)
@@ -147,6 +84,12 @@ static void format_json(VSTREAM *showq_stream)
        quote_buf = vstring_alloc(100);
     }
 
+    /*
+     * Force JSON values to UTF-8 (with SMTPUTF8 enabled) or ASCII (with
+     * SMTPUTF8 disabled).
+     */
+#define QUOTE_JSON(res, src) printable(quote_for_json((res), (src), -1), '?')
+
     /*
      * Read the message properties and sender address.
      */
@@ -162,14 +105,14 @@ static void format_json(VSTREAM *showq_stream)
        msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
     vstream_printf("{");
     vstream_printf("\"queue_name\": \"%s\", ",
-                  json_quote(quote_buf, STR(queue_name)));
+                  QUOTE_JSON(quote_buf, STR(queue_name)));
     vstream_printf("\"queue_id\": \"%s\", ",
-                  json_quote(quote_buf, STR(queue_id)));
+                  QUOTE_JSON(quote_buf, STR(queue_id)));
     vstream_printf("\"arrival_time\": %ld, ", arrival_time);
     vstream_printf("\"message_size\": %ld, ", message_size);
     vstream_printf("\"forced_expire\": %s, ", forced_expire ? "true" : "false");
     vstream_printf("\"sender\": \"%s\", ",
-                  json_quote(quote_buf, STR(addr)));
+                  QUOTE_JSON(quote_buf, STR(addr)));
 
     /*
      * Read zero or more (recipient, reason) pair(s) until attr_scan_more()
@@ -188,10 +131,10 @@ static void format_json(VSTREAM *showq_stream)
                      ATTR_TYPE_END) != 2)
            msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
        vstream_printf("\"address\": \"%s\"",
-                      json_quote(quote_buf, STR(addr)));
+                      QUOTE_JSON(quote_buf, STR(addr)));
        if (LEN(why) > 0)
            vstream_printf(", \"delay_reason\": \"%s\"",
-                          json_quote(quote_buf, STR(why)));
+                          QUOTE_JSON(quote_buf, STR(why)));
        vstream_printf("}");
     }
     vstream_printf("]");
index 5c31d5e1343eb596562be48535a8944f1f2f7171..01211fba02d5e211c45917aaa0b087312d0b3180 100644 (file)
@@ -45,7 +45,7 @@ SRCS  = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
        byte_mask.c known_tcp_ports.c argv_split_at.c dict_stream.c \
        sane_strtol.c hash_fnv.c ldseed.c mkmap_cdb.c mkmap_db.c mkmap_dbm.c \
        mkmap_fail.c mkmap_lmdb.c mkmap_open.c mkmap_sdbm.c inet_prefix_top.c \
-       inet_addr_sizes.c
+       inet_addr_sizes.c quote_for_json.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 \
@@ -91,7 +91,8 @@ OBJS  = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.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 dict_stream.o \
        sane_strtol.o hash_fnv.o ldseed.o mkmap_db.o mkmap_dbm.o \
-       mkmap_fail.o mkmap_open.o inet_prefix_top.o inet_addr_sizes.o
+       mkmap_fail.o mkmap_open.o inet_prefix_top.o inet_addr_sizes.o \
+       quote_for_json.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.
@@ -145,7 +146,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
        vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \
        vbuf_print split_qnameval vstream msg_logger byte_mask \
        known_tcp_ports dict_stream find_inet binhash hash_fnv argv \
-       clean_env inet_prefix_top printable readlline
+       clean_env inet_prefix_top printable readlline quote_for_json
 PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
        $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX)
 HTABLE_FIX = NORANDOMIZE=1
@@ -619,6 +620,11 @@ inet_prefix_top: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
+quote_for_json: $(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 host_port_test dict_tests \
@@ -629,7 +635,7 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        miss_endif_regexp_test split_qnameval_test vstring_test \
        vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \
        binhash_test argv_test inet_prefix_top_test printable_test \
-       valid_utf8_string_test readlline_test
+       valid_utf8_string_test readlline_test quote_for_json_test
  
 dict_tests: all dict_test \
        dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
@@ -1103,6 +1109,9 @@ argv_test: argv
 inet_prefix_top_test: inet_prefix_top
        $(SHLIB_ENV) ${VALGRIND} ./inet_prefix_top
 
+quote_for_json_test: quote_for_json
+       $(SHLIB_ENV) ${VALGRIND} ./quote_for_json
+
 depend: $(MAKES)
        (sed '1,/^# do not edit/!d' Makefile.in; \
        set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -2555,6 +2564,10 @@ printable.o: stringops.h
 printable.o: sys_defs.h
 printable.o: vbuf.h
 printable.o: vstring.h
+quote_for_json.o: quote_for_json.c
+quote_for_json.o: stringops.h
+quote_for_json.o: sys_defs.h
+quote_for_json.o: vstring.h
 rand_sleep.o: iostuff.h
 rand_sleep.o: msg.h
 rand_sleep.o: myrand.h
diff --git a/postfix/src/util/quote_for_json.c b/postfix/src/util/quote_for_json.c
new file mode 100644 (file)
index 0000000..f54af3f
--- /dev/null
@@ -0,0 +1,218 @@
+/*++
+/* NAME
+/*     quote_for_json 3
+/* SUMMARY
+/*     quote UTF-8 string value for JSON
+/* SYNOPSIS
+/*     #include <quote_for_json.h>
+/*
+/*     char    *quote_for_json(
+/*     VSTRING *result,
+/*     const char *in,
+/*     ssize_t len)
+/*
+/*     char    *quote_for_json_append(
+/*     VSTRING *result,
+/*     const char *in,
+/*     ssize_t len)
+/* DESCRIPTION
+/*     quote_for_json() takes well-formed UTF-8 encoded text,
+/*     quotes that text compliant with RFC 4627, and returns a
+/*     pointer to the resulting text. The input may contain null
+/*     bytes, but the output will not.
+/*
+/*     quote_for_json() produces short (two-letter) escape sequences
+/*     for common control characters, double quote and backslash.
+/*     It will not quote "/" (0x2F), and will quote DEL (0x7f) as
+/*     \u007F to make it printable. The input byte sequence "\uXXXX"
+/*     is quoted like any other text (the "\" is escaped as "\\").
+/*
+/*     quote_for_json() does not perform UTF-8 validation. The caller
+/*     should use valid_utf8_string() or printable() as appropriate.
+/*
+/*     quote_for_json_append() appends the output to the result buffer.
+/*
+/*     Arguments:
+/* .IP result
+/*     Storage for the result, resized automatically.
+/* .IP in
+/*     Pointer to the input byte sequence.
+/* .IP len
+/*     The length of the input byte sequence, or a negative number
+/*     when the byte sequence is null-terminated.
+/* DIAGNOSTICS
+/*     Fatal error: memory allocation error.
+/* 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
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <stringops.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+
+/* quote_for_json_append - quote JSON string, append result */
+
+char   *quote_for_json_append(VSTRING *result, const char *text, ssize_t len)
+{
+    const char *cp;
+    int     ch;
+
+    if (len < 0)
+       len = strlen(text);
+
+    for (cp = text; len > 0; len--, cp++) {
+       ch = *(const unsigned char *) cp;
+       if (UNEXPECTED(ISCNTRL(ch))) {
+           switch (ch) {
+           case '\b':
+               VSTRING_ADDCH(result, '\\');
+               VSTRING_ADDCH(result, 'b');
+               break;
+           case '\f':
+               VSTRING_ADDCH(result, '\\');
+               VSTRING_ADDCH(result, 'f');
+               break;
+           case '\n':
+               VSTRING_ADDCH(result, '\\');
+               VSTRING_ADDCH(result, 'n');
+               break;
+           case '\r':
+               VSTRING_ADDCH(result, '\\');
+               VSTRING_ADDCH(result, 'r');
+               break;
+           case '\t':
+               VSTRING_ADDCH(result, '\\');
+               VSTRING_ADDCH(result, 't');
+               break;
+           default:
+               /* All other controls including DEL and NUL. */
+               vstring_sprintf_append(result, "\\u%04X", ch);
+               break;
+           }
+       } else {
+           switch (ch) {
+           case '\\':
+           case '"':
+               VSTRING_ADDCH(result, '\\');
+               /* FALLTHROUGH */
+           default:
+               /* Includes malformed UTF-8. */
+               VSTRING_ADDCH(result, ch);
+               break;
+           }
+       }
+    }
+    VSTRING_TERMINATE(result);
+    return (STR(result));
+}
+
+/* quote_for_json - quote JSON string */
+
+char   *quote_for_json(VSTRING *result, const char *text, ssize_t len)
+{
+    VSTRING_RESET(result);
+    return (quote_for_json_append(result, text, len));
+}
+
+#ifdef TEST
+
+ /*
+  * System library.
+  */
+#include <stdlib.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_vstream.h>
+
+typedef struct TEST_CASE {
+    const char *label;                 /* identifies test case */
+    char   *(*fn) (VSTRING *, const char *, ssize_t);
+    const char *input;                 /* input string */
+    ssize_t input_len;                 /* -1 or input length */
+    const char *exp_res;               /* expected result */
+} TEST_CASE;
+
+#define PASS   (0)
+#define FAIL   (1)
+
+ /*
+  * The test cases.
+  */
+static const TEST_CASE test_cases[] = {
+    {"ordinary ASCII text", quote_for_json,
+       " abcABC012.,[]{}/", -1, " abcABC012.,[]{}/",
+    },
+    {"quote_for_json_append", quote_for_json_append,
+       "foo", -1, " abcABC012.,[]{}/foo",
+    },
+    {"common control characters", quote_for_json,
+       "\b\f\r\n\t", -1, "\\b\\f\\r\\n\\t",
+    },
+    {"uncommon control characters and DEL", quote_for_json,
+       "\0\01\037\040\176\177", 6, "\\u0000\\u0001\\u001F ~\\u007F",
+    },
+    {"malformed UTF-8", quote_for_json,
+       "\\*\\uasd\\u007F\x80", -1, "\\\\*\\\\uasd\\\\u007F\x80",
+    },
+    0,
+};
+
+int     main(int argc, char **argv)
+{
+    const TEST_CASE *tp;
+    int     pass = 0;
+    int     fail = 0;
+    VSTRING *res_buf = vstring_alloc(100);
+
+    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+    for (tp = test_cases; tp->label != 0; tp++) {
+       int     test_fail = 0;
+       char   *res;
+
+       msg_info("RUN  %s", tp->label);
+       res = tp->fn(res_buf, tp->input, tp->input_len);
+       if (strcmp(res, tp->exp_res) != 0) {
+           msg_warn("test case '%s': got '%s', want '%s'",
+                    tp->label, res, tp->exp_res);
+           test_fail = 1;
+       }
+       if (test_fail) {
+           fail++;
+           msg_info("FAIL %s", tp->label);
+           test_fail = 1;
+       } else {
+           msg_info("PASS %s", tp->label);
+           pass++;
+       }
+    }
+    msg_info("PASS=%d FAIL=%d", pass, fail);
+    vstring_free(res_buf);
+    exit(fail != 0);
+}
+
+#endif
index ab38debbe0642b020e7d3fe13b4238a5fed1c960..db56f237a73fdf37096550f88e701a04d4f5ccd7 100644 (file)
@@ -65,6 +65,8 @@ extern size_t balpar(const char *, const char *);
 extern char *WARN_UNUSED_RESULT extpar(char **, const char *, int);
 extern int strcasecmp_utf8x(int, const char *, const char *);
 extern int strncasecmp_utf8x(int, const char *, const char *, ssize_t);
+extern char *quote_for_json(VSTRING *, const char *, ssize_t);
+extern char *quote_for_json_append(VSTRING *, const char *, ssize_t);
 
 #define EXTPAR_FLAG_NONE       (0)
 #define EXTPAR_FLAG_STRIP      (1<<0)  /* "{ text }" -> "text" */