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.
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.
* 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
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).
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.
--- /dev/null
+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.
+
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
$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
$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
$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
$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
$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
<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
<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>
--- /dev/null
+<!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>
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
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 >$@
<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>
<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
--- /dev/null
+<!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> <<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>
<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
<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
# 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
# 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
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
(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
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
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
--- /dev/null
+.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
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;
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;
<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
<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>
--- /dev/null
+<!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>
../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 \
../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 \
../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) >$@
../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) >$@
--- /dev/null
+#++
+# 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
+#--
chunking
allowlists
FWS
+mongodb
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
stringz
Sarvepalli
uXXXX
+Aionda
+Ferraro
+GmbH
+Hamid
+LLC
+Maadani
+MongoDB
+PRId
+bson
+dexo
+hamid
+itoa
+libmongoc
+mongdb
+mongo
+mongodb
+mongodbconf
+Dextrous
+Mongo
+SUD
NONPROD
LC
Philosof
+MONGODB
+Refactored
+Vijay
api
MinProtocol
spammy
+concat
+hamid
+ina
+lbson
+libbson
+libmobgo
+libmongoc
+lmongoc
+mongo
+mongod
+noSQL
+srv
+viktor
+MONGODB
+MongoDB
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 \
# 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 \
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
$(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 \
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
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
--- /dev/null
+/*++
+/* 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
--- /dev/null
+#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
#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>
#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,
* 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
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
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 > $@
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
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
/* 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
#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)
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.
*/
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()
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("]");
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 \
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.
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
$(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 \
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 \
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 \
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
--- /dev/null
+/*++
+/* 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
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" */