Cleanup: documented the myorigin/mydomain address rewriting
in canonical, generic and virtual alias maps.
+ Feature: updated LDAP and *SQL query interfaces using a
+ common infrastructure so that all have the same feature set
+ where possible. Victor Duchovni and many others. This code
+ was tested separately and was merged into the main stream
+ 20050308. Files: global/db_common.[hc], global/dict_ldap.c,
+ global/dict_mysql.c, global/dict_pgsql.c, plus documentation.
+
20050210
Bugfix: spurious fallback_relay warnings after 20050202.
webpage. As proof of authenticity the new PGP key is signed
with Wietse's old PGP key.
+ Cleanup: check_mumble_{ns,mx}_access no longer attempt to
+ do MX or NS lookups for address literals. An address literal
+ is treated as its own MX host; there is no meaningful
+ equivalent for NS access control. File: smtpd/smtpd_check.c.
+
Open problems:
Med: disable header address rewriting after XCLIENT?
is received". Once you've finished reading the remainder of this document, the
table will help you to quickly find what you need.
- _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b
- |A\bAd\bdd\bdr\bre\bes\bss\bs |S\bSc\bco\bop\bpe\be |D\bDa\bae\bem\bmo\bon\bn |G\bGl\blo\bob\bba\bal\bl t\btu\bur\brn\bn-\b-o\bon\bn |S\bSe\bel\ble\bec\bct\bti\biv\bve\be t\btu\bur\brn\bn-\b-o\bof\bff\bf |
- |m\bma\ban\bni\bip\bpu\bul\bla\bat\bti\bio\bon\bn| | |c\bco\bon\bnt\btr\bro\bol\bl |c\bco\bon\bnt\btr\bro\bol\bl |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Rewrite | |trivial-|append_at_myorigin, | |
- |addresses to|all mail|rewrite |append_dot_mydomain,|none |
- |standard | |(8) |swap_bangpath, | |
- |form | | |allow_percent_hack | |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Canonical | |cleanup | | |
- |address |all mail|(8) |canonical_maps |receive_override_options|
- |mapping | | | | |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Address |all mail|cleanup |masquerade_domains |receive_override_options|
- |masquerading| |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Automatic | |cleanup |always_bcc, | |
- |BCC |new mail|(8) |sender_bcc_maps, |receive_override_options|
- |recipients | | |recipient_bcc_maps | |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Virtual |all mail|cleanup |virtual_alias_maps |receive_override_options|
- |aliasing | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Resolve | |trivial-| | |
- |address to |all mail|rewrite |none |none |
- |destination | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Mail | |trivial-| | |
- |transport |all mail|rewrite |transport_maps |none |
- |switch | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Relocated | |trivial-| | |
- |users table |all mail|rewrite |relocated_maps |none |
- | | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Generic |outgoing| | | |
- |mapping |SMTP |smtp(8) |smtp_generic_maps |none |
- |table |mail | | | |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Local alias |local | | | |
- |database |mail |local(8)|alias_maps |none |
- | |only | | | |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Local per- |local | | | |
- |user |mail |local(8)|forward_path |none |
- |.forward |only | | | |
- |files | | | | |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
- |Local catch-|local | | | |
- |all address |mail |local(8)|luser_relay |none |
- | |only | | | |
- |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b
+ |A\bAd\bdd\bdr\bre\bes\bss\bs |S\bSc\bco\bop\bpe\be |D\bDa\bae\bem\bmo\bon\bn |G\bGl\blo\bob\bba\bal\bl t\btu\bur\brn\bn-\b-o\bon\bn |S\bSe\bel\ble\bec\bct\bti\biv\bve\be t\btu\bur\brn\bn-\b-o\bof\bff\bf c\bco\bon\bnt\btr\bro\bol\bl |
+ |m\bma\ban\bni\bip\bpu\bul\bla\bat\bti\bio\bon\bn| | |c\bco\bon\bnt\btr\bro\bol\bl | |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Rewrite | |trivial-|append_at_myorigin, | |
+ |addresses to|all mail|rewrite |append_dot_mydomain,|local_header_rewrite_clients,|
+ |standard | |(8) |swap_bangpath, |remote_header_rewrite_domain |
+ |form | | |allow_percent_hack | |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Canonical | |cleanup | |receive_override_options, |
+ |address |all mail|(8) |canonical_maps |local_header_rewrite_clients,|
+ |mapping | | | |remote_header_rewrite_domain |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Address | |cleanup | |receive_override_options, |
+ |masquerading|all mail|(8) |masquerade_domains |local_header_rewrite_clients,|
+ | | | | |remote_header_rewrite_domain |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Automatic | |cleanup |always_bcc, | |
+ |BCC |new mail|(8) |sender_bcc_maps, |receive_override_options |
+ |recipients | | |recipient_bcc_maps | |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Virtual |all mail|cleanup |virtual_alias_maps |receive_override_options |
+ |aliasing | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Resolve | |trivial-| | |
+ |address to |all mail|rewrite |none |none |
+ |destination | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Mail | |trivial-| | |
+ |transport |all mail|rewrite |transport_maps |none |
+ |switch | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Relocated | |trivial-| | |
+ |users table |all mail|rewrite |relocated_maps |none |
+ | | |(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 _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Generic |outgoing| | | |
+ |mapping |SMTP |smtp(8) |smtp_generic_maps |none |
+ |table |mail | | | |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Local alias |local | | | |
+ |database |mail |local(8)|alias_maps |none |
+ | |only | | | |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Local per- |local | | | |
+ |user |mail |local(8)|forward_path |none |
+ |.forward |only | | | |
+ |files | | | | |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+ |Local catch-|local | | | |
+ |all address |mail |local(8)|luser_relay |none |
+ | |only | | | |
+ |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
A\bAd\bdd\bdr\bre\bes\bss\bs r\bre\bew\bwr\bri\bit\bti\bin\bng\bg w\bwh\bhe\ben\bn m\bma\bai\bil\bl i\bis\bs r\bre\bec\bce\bei\biv\bve\bed\bd
rewrite(8) daemon. The purpose of rewriting to standard form is to reduce the
number of entries needed in lookup tables.
-Postfix versions 2.2 and later do not rewrite message headers from remote SMTP
-clients at all, unless a non-empty domain name is specified with the
-remote_header_rewrite_domain configuration parameter. The
-local_header_rewrite_clients parameter controls what SMTP clients Postfix
-considers local.
+NOTE: Postfix versions 2.2 and later rewrite message headers from remote SMTP
+clients only if the client matches the local_header_rewrite_clients parameter,
+or if the remote_header_rewrite_domain configuration parameter specifies a non-
+empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
The Postfix trivial-rewrite(8) daemon implements the following hard-coded
address manipulations:
of Postfix components expect that all addresses have the form
"user@domain".
- Postfix versions 2.2 and later either do not rewrite message headers
- from remote SMTP clients at all, or they append the domain name
- specified with the remote_header_rewrite_domain configuration
- parameter.
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter; otherwise they append the
+ domain name specified with the remote_header_rewrite_domain
+ configuration parameter, if one is specified. To get the behavior
+ before Postfix 2.2, specify "local_header_rewrite_clients = static:
+ all".
If your machine is not the main machine for $myorigin and you wish to
have some users delivered locally without going via that main machine,
(default: yes). The purpose is to get consistent treatment of different
forms of the same hostname.
- Postfix versions 2.2 and later either do not rewrite message headers
- from remote clients at all, or they append the domain name specified
- with the remote_header_rewrite_domain configuration parameter.
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter; otherwise they append the
+ domain name specified with the remote_header_rewrite_domain
+ configuration parameter, if one is specified. To get the behavior
+ before Postfix 2.2, specify "local_header_rewrite_clients = static:
+ all".
Some will argue that rewriting "host" to "host.domain" is bad. That is
why it can be turned off. Others like the convenience of having
addresses are rewritten; this is controlled with the canonical_classes
configuration parameter.
-Postfix versions 2.2 and later do not rewrite message headers from remote
-clients at all, unless a non-empty domain name is specified with the
-remote_header_rewrite_domain configuration parameter. The
-local_header_rewrite_clients parameter controls what SMTP clients Postfix
-considers local.
+NOTE: Postfix versions 2.2 and later rewrite message headers from remote SMTP
+clients only if the client matches the local_header_rewrite_clients parameter,
+or if the remote_header_rewrite_domain configuration parameter specifies a non-
+empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
Address rewriting is done for local and remote addresses. The mapping is useful
to replace login names by "Firstname.Lastname" style addresses, or to clean up
mail gateway, and to make it appear as if the mail comes from the gateway
itself, instead of from individual machines.
-Postfix versions 2.2 and later do not rewrite message headers from remote SMTP
-clients at all, unless a non-empty domain name is specified with the
-remote_header_rewrite_domain configuration parameter. The
-local_header_rewrite_clients parameter controls what SMTP clients Postfix
-considers local.
+NOTE: Postfix versions 2.2 and later rewrite message headers from remote SMTP
+clients only if the client matches the local_header_rewrite_clients parameter,
+or if the remote_header_rewrite_domain configuration parameter specifies a non-
+empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
Address masquerading is disabled by default, and is implemented by the cleanup
(8) server. To enable, edit the masquerade_domains parameter in the main.cf
* If you use an LDAP map for lookups other than aliases, you may have to make
sure the lookup makes sense. In the case of virtual lookups, maildrops
other than mail addresses are pretty useless, because Postfix can't know
- how to set the ownership for program or file delivery. Your query_filter
+ how to set the ownership for program or file delivery. Your q\bqu\bue\ber\bry\by_\b_f\bfi\bil\blt\bte\ber\br
should probably look something like this:
query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")
some thought on your part to implement safely, considering the
ramifications of this type of delivery. You may decide it's not worth the
bother to allow any of that nonsense in LDAP lookups, ban it in the
- query_filter, and keep things like majordomo lists in local alias
+ q\bqu\bue\ber\bry\by_\b_f\bfi\bil\blt\bte\ber\br, and keep things like majordomo lists in local alias
databases.
query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")
the work on RFC 2254 escaping in queries. Spotted a bug in binding.
* Sami Haahtinen: Referral chasing and v3 support.
* Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones:
- OpenLDAP cache deprecation. Limits on recursion, expansion and query
+ OpenLDAP cache deprecation. Limits on recursion, expansion and search
results size. LDAP connection sharing for maps differing only in the query
parameters.
* Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions
in external files (ldap:/path/ldap.cf) needed to securely store passwords
for plain auth.
+ * Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.
+ * Liviu Daia with further refinements from Jose Luis Tallon and Victor
+ Duchovni developed the common query, result_format, domain and
+ expansion_limit interface for LDAP, MySQL and PosgreSQL.
And of course Wietse.
# The database name on the servers.
dbname = customer_database
-# The table name.
-table = mxaliases
+# For Postfix 2.2 and later The SQL query template.
+# See mysql_table(5) for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
-# Query components, see below.
+# For Postfix releases prior to 2.2. See mysql_table(5) for details.
select_field = forw_addr
+table = mxaliases
where_field = alias
-
-# You may specify additional_conditions or leave this empty.
-additional_conditions = and status = 'paid'
-
-# The above variables will result in a query of the form:
-#
-# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid'
-#
-# ($lookup is escaped so if it contains single quotes or other odd
-# characters, it will not cause trouble).
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
A\bAd\bdd\bdi\bit\bti\bio\bon\bna\bal\bl n\bno\bot\bte\bes\bs
Group, Inc.
* Liviu Daia revised the configuration interface and added the main.cf
configuration feature.
+ * Liviu Daia with further refinements from Jose Luis Tallon and Victor
+ Duchovni developed the common query, result_format, domain and
+ expansion_limit interface for LDAP, MySQL and PosgreSQL.
# The database name on the servers.
dbname = customer_database
-# The table name.
-table = mxaliases
+# Postfix 2.2 and later The SQL query template. See pgsql_table(5).
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
-# Query components, see below.
+# For Postfix releases prior to 2.2. See pgsql_table(5) for details.
select_field = forw_addr
+table = mxaliases
where_field = alias
-
-# You may specify additional_conditions or leave this empty.
-additional_conditions = and status = 'paid'
-
-# The above variables will result in a query of the form:
-#
-# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid'
-#
-# ($lookup is escaped so if it contains single quotes or other odd
-# characters, it will not cause problems).
-#
-# You may also override the built-in SELECT template. See pgsql_table(5)
-# for details.
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
U\bUs\bsi\bin\bng\bg m\bmi\bir\brr\bro\bor\bre\bed\bd d\bda\bat\bta\bab\bba\bas\bse\bes\bs
* LaMont Jones was the initial Postfix pgsql maintainer.
* Liviu Daia revised the configuration interface and added the main.cf
configuration feature.
+ * Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.
+ * Liviu Daia with further refinements from Jose Luis Tallon and Victor
+ Duchovni developed the common query, result_format, domain and
+ expansion_limit interface for LDAP, MySQL and PosgreSQL.
- TLS and IPv6 support are now built into Postfix, based on code
from third-party patches.
+- Extended query interface for LDAP, MySQL and PostgreSQL with free
+form SQL queries, and domain filters to reduce unnecessary lookups.
+
- SMTP client-side connection reuse. This can dramatically speed
up deliveries to high-volume destinations that have some servers
that respond, and some non-responding mail servers.
- By default, message header address rewriting is now disabled for
-SMTP mail from other systems. Thus, spam from poorly written
-software no longer looks like it came from a local user.
+SMTP mail from other systems (including masquerading and canonical
+mapping). Thus, spam from poorly written software no longer looks
+like it came from a local user.
- When your machine does not have its own domain name, Postfix can
now replace your "home network" email address by your ISP account
Major changes - database support
--------------------------------
+[Feature 20050209] Extended LDAP, MySQL and PgSQL query interface
+with free form SQL queries, the domain filter optimization that was
+already available with LDAP and more. This code was worked on by
+many people but Victor Duchovni took the lead. See the respective
+{LDAP,MYSQL,PGSQL}_README and {ldap,mysql,pgsql}_table documents.
+
[Feature 20041210] You can now dump an entire database with the new
postmap/postalias "-s" option. This works only for database types
with Postfix sequence operator support: hash, btree, dbm, and sdbm.
+++ /dev/null
-#!/bin/sh
-# postfinger - captures Postfix configuration for reporting errors
-#
-# Inspired by comments on the postfix-users mailing list.
-# Copyright (C) 2003 Simon J. Mudd (sjmudd@pobox.com)
-# With help from:
-# Matthias Andree <ma@dt.e-technik.uni-dortmund.de>
-# Victor Duchovni <Victor.Duchovni@morganstanley.com>
-# Sasa Babic <sasab@hygia.pharmacy.bg.ac.yu>
-# Iñaki Arenaza <iarenaza@escomposlinux.org>
-# Jorge Gordoy <gordoy@g2ctech.com>
-# $Revision: 1.29 $
-#
-# License:
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You may have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
-# USA.
-#
-# An on-line copy of the GNU General Public License can be found
-# http://www.fsf.org/copyleft/gpl.html.
-
-version_number=1.29 # don't use rcs version here
-version="version: ${version_number}"
-BACKUP_IFS=$IFS
-usage="postfinger ${version}: a Postfix configuration extraction utility
-Usage: postfinger [options]
-
-Options can be any of:
- --all Show all configuration information
- --system Show basic system environment (os/kernel/...) [default]
- --package Show packaging information [default]
- --locking Show mailbox locking methods
- --tables Show supported lookup tables
- --main Show main.cf non-default configuration values [default]
- --defaultsinmain Show main.cf defined values which are identical to defaults
- --master Show master.cf configuration [default]
- --permissions Show some of the spool_directory permissions
- --libraries Show the Postfix libraries dependencies
-
- --nosystem Do not show basic system environment (os/kernel/...)
- --nomain Do not show main.cf non-default configuration values
- --nomaster Do not show master.cf configuration
- --nowarn Do not warn about private information being leaked to
- outsiders
- --version print the version of postfinger being used and exit
-
-Mail bug reports and suggestions to <postfinger@WL0.org>".
-
-system=1; package=1; locking=; tables=; main=1; master=1; permissions=; libraries=;warn=1;defaultsinmain=
-
-for arg
-do
- case $arg in
- --version) echo "postfinger ${version}"; exit 0;;
- --all) system=1; package=1; locking=1; tables=1; main=1; master=1; permissions=1; libraries=1; warn=1;;
- --system) system=1;;
- --package) package=1;;
- --locking) locking=1;;
- --tables) tables=1;;
- --main) main=1;;
- --defaultsinmain) defaultsinmain=1;;
- --master) master=1;;
- --permissions) permissions=1;;
- --libraries) libraries=1;;
- --nosystem) system=;;
- --nomain) main=;;
- --nomaster) master=;;
- --nowarn) warn=;;
- --help) echo "${usage}"; exit 0;;
- *) echo "Error: ${usage}" 1>&2; exit 1;;
- esac
- shift
-done
-
-echo "postfinger - postfix configuration on `LANG=C date`"
-echo ${version}
-echo ''
-
-[ "${warn}" = 1 ] && {
-cat <<END
-Warning: postfinger output may show private configuration information,
-such as ip addresses and/or domain names which you do not want to show
-to the public. If this is the case it is your responsibility to modify
-the output to hide this private information. [Remove this warning with
-the --nowarn option.]
-
-END
-}
-
-# Look for postconf, using environment variable if given
-[ -n "${POSTCONF}" ] && [ ! -x "${POSTCONF}" ] && POSTCONF=
-[ -z "${POSTCONF}" ] && [ -x /usr/sbin/postconf ] && POSTCONF=/usr/sbin/postconf
-[ -z "${POSTCONF}" ] && [ -x /usr/local/sbin/postconf ] && POSTCONF=/usr/local/sbin/postconf
-[ -z "${POSTCONF}" ] && {
- echo "$0: can not find postconf"
- echo "set POSTCONF to postconf's location and try again"
- exit 1
-}
-
-# Look for smtpd, using environment variable if given
-[ -z "${SMTPD}" ] && {
- SMTPD=`${POSTCONF} -h daemon_directory`/smtpd
- [ -d "${SMTPD}" -o ! -x "${SMTPD}" ] && SMTPD=
-}
-[ -z "${SMTPD}" ] && {
- echo "$0: can not find smtpd"
- echo "set SMTPD to smtpd's location and try again"
- exit 1
-}
-
-[ "${system}" = 1 ] && {
- echo '--System Parameters--'
- ${POSTCONF} -d mail_version
- echo "hostname = `hostname`"
- echo "uname = `uname -a`"
- echo ""
-}
-
-# check for different packaging systems and try to identify if this postfix
-# (smtpd) comes from a package.
-# I would appreciate help in adapting this part to include other packaging
-# systems.
-[ "${package}" = 1 ] && {
- echo "--Packaging information--"
-
- DPKG=
- [ -x /usr/bin/dpkg ] && DPKG=/usr/bin/dpkg
- [ -z "${DPKG}" ] && [ -x /usr/local/bin/dpkg ] && DPKG=/usr/local/bin/dpkg
- [ -n "${DPKG}" ] && {
- ${DPKG} -S ${SMTPD} >/dev/null 2>/dev/null && {
- package=`${DPKG} -S ${SMTPD} | awk -F: '{print $1}' | head -n 1`
- package_ver=`COLUMNS=132 ${DPKG} -l ${package} | grep ii | grep -v "documentation" | awk '{print $3}'`
- echo "looks like this postfix comes from deb package: ${package}-${package_ver}"
- }
- }
-
- RPM=
- [ -x /bin/rpm ] && RPM=/bin/rpm
- [ -z "${RPM}" ] && [ -x /usr/local/bin/rpm ] && RPM=/usr/local/bin/rpm
- [ -n "${RPM}" ] && {
- ${RPM} -qf ${SMTPD} >/dev/null 2>/dev/null && \
- echo "looks like this postfix comes from RPM package: `${RPM} -qf ${SMTPD}`"
- }
-
- BSDPKG=
- [ -x /usr/sbin/pkg_info ] && BSDPKG=/usr/sbin/pkg_info
- [ -n "${BSDPKG}" ] && {
- ${BSDPKG} -q -W ${SMTPD} >/dev/null 2>/dev/null && \
- echo "looks like this postfix comes from BSD package: `${BSDPKG} -q -W ${SMTPD}`"
- }
-
- echo ""
-}
-
-IFS="
-"
-[ "${locking}" = 1 ] && {
- echo "--Mailbox locking methods--"
- locking_methods=`${POSTCONF} -l`
- echo $locking_methods
- echo ""
-}
-
-[ "${tables}" = 1 ] && {
- echo "--Supported Lookup tables--"
- lookup_tables=`${POSTCONF} -m`
- echo $lookup_tables
- echo ""
-}
-
-[ "${main}" = 1 -o "${defaultsinmain}" = 1 ] && {
- if [ "x`find . -prune \( -perm 020 -o -perm 002 \) -print`" != "x" ]
- then
- echo 2>&2 "Do not run this in a public- or group-writable directory"
- exit 1
- fi
-
- rm -f postfinger.$$.d postfinger.$$.n
- ${POSTCONF} -d | tr -s [:blank:] | sort > postfinger.$$.d
- ${POSTCONF} -n | tr -s [:blank:] | sort > postfinger.$$.n
-
- [ "$main" = 1 ] && {
- echo "--main.cf non-default parameters--"
- comm -13 postfinger.$$.d postfinger.$$.n
- echo ""
- }
-
- [ "${defaultsinmain}" = 1 ] && {
- echo "--main.cf parameters defined as per defaults--"
- comm -12 postfinger.$$.d postfinger.$$.n
- echo ""
- }
-
- rm -f postfinger.$$.d postfinger.$$.n
-}
-
-[ "${master}" = 1 ] && {
- echo "--master.cf--"
- # Remove blank and commented lines to reduce the output
- # Note: the second grep contains a space followed by a tab character
- cat `${POSTCONF} -h config_directory`/master.cf | \
- grep -v '^#' | \
- grep -v '^[ ]*$'
- echo ""
-}
-
-[ "${permissions}" = 1 ] && {
- echo "--Specific file and directory permissions--"
- ls -ld `${POSTCONF} -h queue_directory`/maildrop
- ls -ld `${POSTCONF} -h queue_directory`/public
- ls -l `${POSTCONF} -h queue_directory`/public 2>/dev/null || {
- echo 'WARNING: No access to $queue_directory/public'
- echo ' Try running postfinger as user root or postfix'
- }
- ls -ld `${POSTCONF} -h queue_directory`/private
- ls -l `${POSTCONF} -h queue_directory`/private 2>/dev/null || {
- echo 'WARNING: No access to $queue_directory/private'
- echo ' Try running postfinger as user root or postfix'
- }
- ls -l `${POSTCONF} -h command_directory`/postdrop
- ls -l `${POSTCONF} -h command_directory`/postqueue
- echo ""
-}
-
-[ "${libraries}" = 1 ] && {
- echo "--Library dependencies--"
- echo "${SMTPD}:"
- ldd ${SMTPD} || echo "WARNING: Can not find ldd. Check you have it installed and in your path"
-}
-
-echo "-- end of postfinger output --"
if [ -z "$has_lrm" -a -z "$has_lrjc" ]
then
echo SAFETY: editing main.cf, setting $unknown_local=450.
- echo See the RELEASE_NOTES and LOCAL_RECIPIENT_README files for details.
+ echo See the LOCAL_RECIPIENT_README file for details.
$POSTCONF -e "$unknown_local = 450" || exit 1
fi
<tr> <td> <a href="#standard"> Rewrite addresses to standard form</a>
</td> <td nowrap> all mail </td> <td> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a> </td>
<td> <a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a>, <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>, <a href="postconf.5.html#swap_bangpath">swap_bangpath</a>,
-<a href="postconf.5.html#allow_percent_hack">allow_percent_hack</a> </td> <td> none </td> </tr>
+<a href="postconf.5.html#allow_percent_hack">allow_percent_hack</a> </td> <td> <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> </td> </tr>
-<tr> <td> <a href="#canonical"> Canonical address mapping </a>
-</td> <td nowrap> all mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td>
-<a href="postconf.5.html#canonical_maps">canonical_maps</a> </td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a> </td> </tr>
+<tr> <td> <a href="#canonical"> Canonical address mapping </a> </td>
+<td nowrap> all mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#canonical_maps">canonical_maps</a>
+</td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a>, <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> </td> </tr>
-<tr> <td> <a href="#canonical"> Address masquerading </a> </td>
-<td nowrap> all mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#masquerade_domains">masquerade_domains</a>
-</td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a> </td> </tr>
+<tr> <td> <a href="#masquerade"> Address masquerading </a> </td> <td
+nowrap> all mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#masquerade_domains">masquerade_domains</a>
+</td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a>, <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> </td> </tr>
<tr> <td> <a href="#auto_bcc"> Automatic BCC recipients </a> </td>
<td nowrap> new mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#always_bcc">always_bcc</a>,
form is to reduce the number of entries needed in lookup tables.
</p>
-<p> Postfix versions 2.2 and later do not rewrite message headers
-from remote SMTP clients at all, unless a non-empty domain name is
-specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration
-parameter. The <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter controls
-what SMTP clients Postfix considers local. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
<p> The Postfix <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> daemon implements the following
hard-coded address manipulations: </p>
because a lot of Postfix components expect that all addresses have
the form "user@domain". </p>
-<p> Postfix versions 2.2 and later either do not rewrite message
-headers from remote SMTP clients at all, or they append the domain
-name specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration
-parameter. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter; otherwise they append the
+domain name specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a>
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
+static:all". </p>
<p> If your machine is not the main machine for $<a href="postconf.5.html#myorigin">myorigin</a> and you
wish to have some users delivered locally without going via that
parameter (default: yes). The purpose is to get consistent treatment
of different forms of the same hostname. </p>
-<p> Postfix versions 2.2 and later either do not rewrite message
-headers from remote clients at all, or they append the domain name
-specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration
-parameter. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter; otherwise they append the
+domain name specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a>
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
+static:all". </p>
<p> Some will argue that rewriting "host" to "host.domain"
is bad. That is why it can be turned off. Others like the convenience
all header and envelope addresses are rewritten; this is controlled
with the <a href="postconf.5.html#canonical_classes">canonical_classes</a> configuration parameter. </p>
-<p> Postfix versions 2.2 and later do not rewrite message headers
-from remote clients at all, unless a non-empty domain name is
-specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration
-parameter. The <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter controls
-what SMTP clients Postfix considers local. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
<p> Address rewriting is
done for local and remote addresses. The mapping is useful to
comes from the gateway itself, instead of from individual machines.
</p>
-<p> Postfix versions 2.2 and later do not rewrite message headers
-from remote SMTP clients at all, unless a non-empty domain name is
-specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration
-parameter. The <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter controls
-what SMTP clients Postfix considers local. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
<p> Address masquerading is disabled by default, and is implemented
by the <a href="cleanup.8.html">cleanup(8)</a> server. To enable, edit the <a href="postconf.5.html#masquerade_domains">masquerade_domains</a>
make sure the lookup makes sense. In the case of virtual lookups,
maildrops other than mail addresses are pretty useless, because
Postfix can't know how to set the ownership for program or file
- delivery. Your query_filter should probably look something like this: </p>
+ delivery. Your <b>query_filter</b> should probably look something like this: </p>
<blockquote>
<pre>
require some thought on your part to implement safely, considering the
ramifications of this type of delivery. You may decide it's not worth
the bother to allow any of that nonsense in LDAP lookups, ban it in
- the query_filter, and keep things like majordomo lists in local alias
+ the <b>query_filter</b>, and keep things like majordomo lists in local alias
databases. </p>
<blockquote>
<li>Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones:
OpenLDAP cache deprecation. Limits on recursion, expansion
- and query results size. LDAP connection sharing for maps
+ and search results size. LDAP connection sharing for maps
differing only in the query parameters.
<li>Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions in
external files (<a href="ldap_table.5.html">ldap</a>:/path/ldap.cf) needed to securely store
passwords for plain auth.
+<li>Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.</li>
+
+<li>Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
+
</ul>
And of course Wietse.
# The database name on the servers.
dbname = customer_database
-# The table name.
-table = mxaliases
+# For Postfix 2.2 and later The SQL query template.
+# See <a href="mysql_table.5.html">mysql_table(5)</a> for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
-# Query components, see below.
+# For Postfix releases prior to 2.2. See <a href="mysql_table.5.html">mysql_table(5)</a> for details.
select_field = forw_addr
+table = mxaliases
where_field = alias
-
-# You may specify additional_conditions or leave this empty.
-additional_conditions = and status = 'paid'
-
-# The above variables will result in a query of the form:
-#
-# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid'
-#
-# ($lookup is escaped so if it contains single quotes or other odd
-# characters, it will not cause trouble).
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
</pre>
<h2>Additional notes</h2>
<ul>
<li> The initial version was contributed by Scott Cotton and Joshua
-Marcus, IC Group, Inc.
+Marcus, IC Group, Inc.</li>
+
+<li> Liviu Daia revised the configuration interface and added the
+main.cf configuration feature.</li>
-<li>Liviu Daia revised the configuration interface and added the
-main.cf configuration feature.
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
</ul>
# The database name on the servers.
dbname = customer_database
-# The table name.
-table = mxaliases
+# Postfix 2.2 and later The SQL query template. See <a href="pgsql_table.5.html">pgsql_table(5)</a>.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
-# Query components, see below.
+# For Postfix releases prior to 2.2. See <a href="pgsql_table.5.html">pgsql_table(5)</a> for details.
select_field = forw_addr
+table = mxaliases
where_field = alias
-
-# You may specify additional_conditions or leave this empty.
-additional_conditions = and status = 'paid'
-
-# The above variables will result in a query of the form:
-#
-# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid'
-#
-# ($lookup is escaped so if it contains single quotes or other odd
-# characters, it will not cause problems).
-#
-# You may also override the built-in SELECT template. See <a href="pgsql_table.5.html">pgsql_table(5)</a>
-# for details.
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
</pre>
<h2>Using mirrored databases</h2>
<ul>
<li> This code is based upon the Postfix mysql map by Scott Cotton
-and Joshua Marcus, IC Group, Inc.
+and Joshua Marcus, IC Group, Inc.</li>
-<li> The PostgreSQL changes were done by Aaron Sethman.
+<li> The PostgreSQL changes were done by Aaron Sethman.</li>
<li> Updates for Postfix 1.1.x and PostgreSQL 7.1+ and support for
-calling stored procedures were added by Philip Warner.
+calling stored procedures were added by Philip Warner.</li>
-<li> LaMont Jones was the initial Postfix pgsql maintainer.
+<li> LaMont Jones was the initial Postfix pgsql maintainer.</li>
<li> Liviu Daia revised the configuration interface and added the
-main.cf configuration feature.
+main.cf configuration feature.</li>
+
+<li> Liviu Daia revised the configuration interface and added the main.cf
+configuration feature.</li>
+
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
</ul>
Support for this form will be removed in a future Postfix
version.
+ Postfix 2.2 has enhanced query interfaces for MySQL and
+ PostgreSQL, these now include features previously avail-
+ able only in the Postfix LDAP client. This work also cre-
+ ated an opportunity for improvements in the LDAP inter-
+ face. The primary compatibility issue is that <b>result_fil-</b>
+ <b>ter</b> (a name that has caused some confusion as to its mean-
+ ing in the past) has been renamed to <b>result_format</b>. For
+ backwards compatibility with the pre 2.2 LDAP client,
+ <b>result_filter</b> can for now be used instead of <b>result_for-</b>
+ <b>mat</b>, when the latter parameter is not also set. The new
+ name better reflects the function of the parameter. This
+ compatibility interface may be removed in a future
+ release.
+
<b>LIST MEMBERSHIP</b>
When using LDAP to store lists such as $<a href="postconf.5.html#mynetworks">mynetworks</a>,
$<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>,
The port the LDAP server listens on, e.g.
server_port = 778
+ <b>timeout (default: 10 seconds)</b>
+ The number of seconds a search can take before tim-
+ ing out, e.g.
+ timeout = 5
+
<b>search_base (No default; you must configure this)</b>
The <a href="http://www.faqs.org/rfcs/rfc2253.html">RFC2253</a> base DN at which to conduct the search,
e.g.
search_base = dc=your, dc=com
- <b>timeout (default: 10 seconds)</b>
- The number of seconds a search can take before tim-
- ing out, e.g.
- timeout = 5
+ With Postfix 2.2 and later 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. <a href="http://www.faqs.org/rfcs/rfc2253.html">RFC 2253</a>
+ quoting is used to make sure that the input
+ key does not add unexpected metacharacters.
+
+ <b>%u</b> When the input key is an address of the form
+ user@domain, <b>%u</b> is replaced by the (RFC
+ 2253) quoted local part of the address.
+ Otherwise, <b>%u</b> is replaced by the entire
+ search string. If the localpart is empty,
+ the search is suppressed and returns no
+ results.
+
+ <b>%d</b> When the input key is an address of the form
+ user@domain, <b>%d</b> is replaced by the (RFC
+ 2253) quoted domain part of the address.
+ Otherwise, the search is suppressed and
+ returns no results.
+
+ <b>%[SUD]</b> For the <b>search_base</b> parameter, the upper-
+ case equivalents of the above expansions
+ behave identically to their lower-case
+ counter-parts. With the <b>result_format</b> param-
+ eter (previously called <b>result_filter</b> see
+ the COMPATIBILITY section and below), they
+ expand to the corresponding components of
+ input key rather than the result value.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by
+ the corresponding 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>. If the input key
+ is unqualified or does not have enough
+ domain components to satisfy all the speci-
+ fied patterns, the search is suppressed and
+ returns no results.
<b>query_filter (default: mailacceptinggeneralid=%s)</b>
The <a href="http://www.faqs.org/rfcs/rfc2254.html">RFC2254</a> filter used to search the directory,
This parameter supports the following '%' expan-
sions:
- <b>%s</b> This is replaced by the input key. <a href="http://www.faqs.org/rfcs/rfc2254.html">RFC 2254</a>
- quoting is used to make sure that the input
- key does not add unexpected metacharacters.
+ <b>%%</b> This is replaced by a literal '%' character.
+ (Postfix 2.2 and later).
+
+ <b>%s</b> This is replaced by the input key. <a href="http://www.faqs.org/rfcs/rfc2254.html">RFC 2254</a>
+ quoting is used to make sure that the input
+ key does not add unexpected metacharacters.
<b>%u</b> When the input key is an address of the form
- user@domain, <b>%u</b> is replaced by the (RFC
- 2254) quoted local part of the address. Oth-
- erwise, <b>%u</b> is replaced by the entire search
- string.
+ user@domain, <b>%u</b> is replaced by the (RFC
+ 2254) quoted local part of the address.
+ Otherwise, <b>%u</b> is replaced by the entire
+ search string. If the localpart is empty,
+ the search is suppressed and returns no
+ results.
<b>%d</b> When the input key is an address of the form
- user@domain, <b>%d</b> is replaced by the (RFC
- 2254) quoted domain part of the address.
- Otherwise, <b>%d</b> is replaced by the entire
- search string.
+ user@domain, <b>%d</b> is replaced by the (RFC
+ 2254) quoted domain part of the address.
+ Otherwise, the search is suppressed and
+ returns no results.
+
+ <b>%[SUD]</b> The upper-case equivalents of the above
+ expansions behave in the <b>query_filter</b> param-
+ eter identically to their lower-case
+ counter-parts. With the <b>result_format</b> param-
+ eter (previously called <b>result_filter</b> see
+ the COMPATIBILITY section and below), they
+ expand to the corresponding components of
+ input key rather than the result value.
+
+ The above %S, %U and %D expansions are
+ available with Postfix 2.2 and later.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by
+ the corresponding 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>. If the input key
+ is unqualified or does not have enough
+ domain components to satisfy all the speci-
+ fied patterns, the saerch is suppressed and
+ returns no results.
+
+ The above %1, ..., %9 expansions are avail-
+ able with Postfix 2.2 and later.
The "domain" parameter described below limits the
input keys to addresses in matching domains. When
matching domains are suppressed and return no
results.
- NOTE: DO NOT put quotes around the query filter.
+ NOTE: DO NOT put quotes around the <b>query_filter</b>
+ parameter.
- <b>result_filter (default: %s</b>)
- Format template applied to result attributes. Sup-
- ports the same expansions as the query_filter, and
- can be easily used to append (or prepend) text.
- This parameter supports the following '%' expan-
- sions:
+ <b>result_format (default: %s</b>)
+ Called <b>result_filter</b> in Postfix releases prior to
+ 2.2. Format template applied to result attributes.
+ Most commonly used to append (or prepend) text to
+ the result. This parameter supports the following
+ '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+ (Postfix 2.2 and later).
<b>%s</b> This is replaced by the value of the result
- attribute.
+ attribute. When result is empty it is
+ skipped.
- <b>%u</b> When the result attribute value is an
+ <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.
- Otherwise, <b>%u</b> is replaced by the entire
- attribute value.
-
- <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.
- Otherwise, <b>%d</b> is replaced by the entire
- attribute value.
-
- For example, using "result_filter = <a href="smtp.8.html">smtp</a>:[%s]"
+ 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 behaviour 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 suppressed and return no
+ results.
+
+ The above %S, %U, %D and %1, ..., %9 expan-
+ sions are available with Postfix 2.2 and
+ later.
+
+ 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
- filter, multiple values are concatenated as comma
+ format, multiple values are concatenated as comma
separated strings. The expansion_limit and
size_limit parameters explained below allow one to
restrict the number of values in the result, which
The default value <b>%s</b> specifies that each attribute
value should be used as is.
- NOTE: DO NOT put quotes around the result filter!
+ This parameter was called <b>result_filter</b> in Postfix
+ releases prior to 2.2. If no "result_format" is
+ specified, the value of "result_filter" will be
+ used instead before resorting to the default value.
+ This provides compatibility with old configuration
+ files.
+
+ NOTE: DO NOT put quotes around the result format!
<b>domain (default: no domain list)</b>
- This is a list of domain names, paths to files, or
- dictionaries. 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
+ This is a list of domain names, paths to files, or
+ dictionaries. 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 LDAP server.
domain = postfix.org, hash:/etc/postfix/search-
domains
- It is best not to use LDAP to store the domains
+ It is best not to use LDAP to store the domains
eligible for LDAP lookups.
- NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a>
+ NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a>
aliases.
<b>result_attribute (default: maildrop)</b>
- The attribute(s) Postfix will read from any direc-
+ The attribute(s) Postfix will read from any direc-
tory entries returned by the lookup, to be resolved
to an email address.
- result_attribute = mailbox,maildrop
+ result_attribute = mailbox, maildrop
<b>special_result_attribute (No default)</b>
The attribute(s) of directory entries that can con-
- tain DNs or URLs. If found, a recursive subsequent
+ tain DNs or URLs. If found, a recursive subsequent
search is done using their values.
special_result_attribute = member
- DN recursion retrieves the same result_attributes
+ DN recursion retrieves the same result_attributes
as the main query, including the special attributes
- for further recursion. URI processing retrieves
- only those attributes that are included in the URI
- definition and are *also* listed in
- "result_attribute". If the URI lists any of the
- map's special result attributes, these are also
+ for further recursion. URI processing retrieves
+ only those attributes that are included in the URI
+ definition and are *also* listed in
+ "result_attribute". If the URI lists any of the
+ map's special result attributes, these are also
retrieved and used recursively.
<b>scope (default: sub)</b>
- The LDAP search scope: <b>sub</b>, <b>base</b>, or <b>one</b>. These
+ The LDAP search scope: <b>sub</b>, <b>base</b>, or <b>one</b>. These
translate into LDAP_SCOPE_SUBTREE, LDAP_SCOPE_BASE,
and LDAP_SCOPE_ONELEVEL.
<b>bind (default: yes)</b>
- Whether or not to bind to the LDAP server. Newer
+ Whether or not to bind to the LDAP server. Newer
LDAP implementations don't require clients to bind,
which saves time. Example:
bind = no
- If you do need to bind, you might consider config-
- uring Postfix to connect to the local machine on a
- port that's an SSL tunnel to your LDAP server. If
- your LDAP server doesn't natively support SSL, put
+ If you do need to bind, you might consider config-
+ uring Postfix to connect to the local machine on a
+ port that's an SSL tunnel to your LDAP server. If
+ your LDAP server doesn't natively support SSL, put
a tunnel (wrapper, proxy, whatever you want to call
- it) on that system too. This should prevent the
- password from traversing the network in the clear.
+ it) on that system too. This should prevent the
+ password from traversing the network in the clear.
<b>bind_dn (default: empty)</b>
- If you do have to bind, do it with this distin-
+ If you do have to bind, do it with this distin-
guished name. Example:
bind_dn = uid=postfix, dc=your, dc=com
<b>bind_pw (default: empty)</b>
- The password for the distinguished name above. If
+ The password for the distinguished name above. If
you have to use this, you probably want to make the
map configuration file readable only by the Postfix
- user. When using the obsolete <a href="ldap_table.5.html">ldap</a>:ldapsource syn-
+ user. When using the obsolete <a href="ldap_table.5.html">ldap</a>:ldapsource syn-
tax, with map parameters in main.cf, it is not pos-
- sible to securely store the bind password. This is
+ sible to securely store the bind password. This is
because main.cf needs to be world readable to allow
local accounts to submit mail via the sendmail com-
mand. Example:
<b>cache_expiry (IGNORED with a warning)</b>
<b>cache_size (IGNORED with a warning)</b>
- The above parameters are NO LONGER SUPPORTED by
+ The above parameters are NO LONGER SUPPORTED by
Postfix. Cache support has been dropped from
OpenLDAP as of release 2.1.13.
<b>recursion_limit (default: 1000)</b>
- A limit on the nesting depth of DN and URL special
- result attribute evaluation. The limit must be a
+ A limit on the nesting depth of DN and URL special
+ result attribute evaluation. The limit must be a
non-zero positive number.
<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
+ 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>size_limit (default: $expansion_limit)</b>
- A limit on the number of LDAP entries returned by
- any single LDAP query performed as part of the
- lookup. A setting of 0 disables the limit. Expan-
- sion of DN and URL references involves nested LDAP
- queries, each of which is separately subjected to
+ A limit on the number of LDAP entries returned by
+ any single LDAP search performed as part of the
+ lookup. A setting of 0 disables the limit. Expan-
+ sion of DN and URL references involves nested LDAP
+ queries, each of which is separately subjected to
this limit.
- Note: even a single LDAP entry can generate multi-
- ple lookup results, via multiple result attributes
- and/or multi-valued result attributes. This limit
- caps the per query resource utilization on the LDAP
- server, not the final multiplicity of the lookup
- result. It is analogous to the "-z" option of
- "ldapsearch".
+ Note: even a single LDAP entry can generate multi-
+ ple lookup results, via multiple result attributes
+ and/or multi-valued result attributes. This limit
+ caps the per search resource utilization on the
+ LDAP server, not the final multiplicity of the
+ lookup result. It is analogous to the "-z" option
+ of "ldapsearch".
<b>dereference (default: 0)</b>
- When to dereference LDAP aliases. (Note that this
+ When to dereference LDAP aliases. (Note that this
has nothing do with Postfix aliases.) The permitted
- values are those legal for the OpenLDAP/UM LDAP
+ values are those legal for the OpenLDAP/UM LDAP
implementations:
0 never
3 always
See ldap.h or the ldap_open(3) or ldapsearch(1) man
- pages for more information. And if you're using an
+ pages for more information. And if you're using an
LDAP package that has other possible values, please
- bring it to the attention of the postfix-
+ bring it to the attention of the postfix-
users@postfix.org mailing list.
<b>chase_referrals (default: 0)</b>
- Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP
+ Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP
version 3 support).
<b>version (default: 2)</b>
Specifies the LDAP protocol version to use.
<b>debuglevel (default: 0)</b>
- What level to set for debugging in the OpenLDAP
+ What level to set for debugging in the OpenLDAP
libraries.
<b>LDAP SSL AND STARTTLS PARAMETERS</b>
- If you're using the OpenLDAP libraries compiled with SSL
- support, Postfix can connect to LDAP SSL servers and can
+ If you're using the OpenLDAP libraries compiled with SSL
+ support, Postfix can connect to LDAP SSL servers and can
issue the STARTTLS command.
- LDAP SSL service can be requested by using a LDAP SSL URL
+ LDAP SSL service can be requested by using a LDAP SSL URL
in the server_host parameter:
server_host = ldaps://ldap.example.com:636
STARTTLS can be turned on with the start_tls parameter:
start_tls = yes
- Both forms require LDAP protocol version 3, which has to
+ Both forms require LDAP protocol version 3, which has to
be set explicitly with:
version = 3
If any of the Postfix programs querying the map is config-
- ured in master.cf to run chrooted, all the certificates
+ ured in master.cf to run chrooted, all the certificates
and keys involved have to be copied to the chroot jail. Of
- course, the private keys should only be readable by the
+ course, the private keys should only be readable by the
user "postfix".
- The following parameters are relevant to LDAP SSL and
+ The following parameters are relevant to LDAP SSL and
STARTTLS:
<b>start_tls (default: no)</b>
Whether or not to issue STARTTLS upon connection to
- the server. Don't set this with LDAP SSL (the SSL
+ the server. Don't set this with LDAP SSL (the SSL
session is setup automatically when the TCP connec-
tion is opened).
- <b>tls_ca_cert_dir (No default; set either this or</b>
+ <b>tls_ca_cert_dir (No default; set either this or</b>
<b>tls_ca_cert_file)</b>
Directory containing X509 Certificate Authority
- certificates in PEM format which are to be recog-
- nized by the client in SSL/TLS connections. The
- files each contain one CA certificate. The files
- are looked up by the CA subject name hash value,
- which must hence be available. If more than one CA
- certificate with the same name hash value exist,
- the extension must be different (e.g. 9d66eef0.0,
- 9d66eef0.1 etc). The search is performed in the
- ordering of the extension number, regardless of
+ certificates in PEM format which are to be recog-
+ nized by the client in SSL/TLS connections. The
+ files each contain one CA certificate. The files
+ are looked up by the CA subject name hash value,
+ which must hence be available. If more than one CA
+ certificate with the same name hash value exist,
+ the extension must be different (e.g. 9d66eef0.0,
+ 9d66eef0.1 etc). The search is performed in the
+ ordering of the extension number, regardless of
other properties of the certificates. Use the
c_rehash utility (from the OpenSSL distribution) to
create the necessary links.
- <b>tls_ca_cert_file (No default; set either this or</b>
+ <b>tls_ca_cert_file (No default; set either this or</b>
<b>tls_ca_cert_dir)</b>
File containing the X509 Certificate Authority cer-
- tificates in PEM format which are to be recognized
- by the client in SSL/TLS connections. This setting
+ tificates in PEM format which are to be recognized
+ by the client in SSL/TLS connections. This setting
takes precedence over tls_ca_cert_dir.
<b>tls_cert (No default; you must set this)</b>
- File containing client's X509 certificate to be
+ File containing client's X509 certificate to be
used by the client in SSL/ TLS connections.
<b>tls_key (No default; you must set this)</b>
- File containing the private key corresponding to
+ File containing the private key corresponding to
the above tls_cert.
<b>tls_require_cert (default: no)</b>
Whether or not to request server's X509 certificate
- and check its validity when establishing SSL/TLS
+ and check its validity when establishing SSL/TLS
connections.
<b>tls_random_file (No default)</b>
- Path of a file to obtain random bits from when
- /dev/[u]random is not available, to be used by the
+ Path of a file to obtain random bits from when
+ /dev/[u]random is not available, to be used by the
client in SSL/TLS connections.
<b>tls_cipher_suite (No default)</b>
Cipher suite to use in SSL/TLS negotiations.
<b>EXAMPLE</b>
- Here's a basic example for using LDAP to look up <a href="local.8.html">local(8)</a>
+ Here's a basic example for using LDAP to look up <a href="local.8.html">local(8)</a>
aliases. Assume that in main.cf, you have:
<a href="postconf.5.html#alias_maps">alias_maps</a> = hash:/etc/aliases,
<a href="ldap_table.5.html">ldap</a>:/etc/postfix/ldap-aliases.cf
server_host = ldap.my.com
search_base = dc=my, dc=com
- Upon receiving mail for a local address "ldapuser" that
- isn't found in the /etc/aliases database, Postfix will
- search the LDAP server listening at port 389 on
- ldap.my.com. It will bind anonymously, search for any
- directory entries whose mailacceptinggeneralid attribute
- is "ldapuser", read the "maildrop" attributes of those
- found, and build a list of their maildrops, which will be
- treated as <a href="http://www.faqs.org/rfcs/rfc822.html">RFC822</a> addresses to which the message will be
+ Upon receiving mail for a local address "ldapuser" that
+ isn't found in the /etc/aliases database, Postfix will
+ search the LDAP server listening at port 389 on
+ ldap.my.com. It will bind anonymously, search for any
+ directory entries whose mailacceptinggeneralid attribute
+ is "ldapuser", read the "maildrop" attributes of those
+ found, and build a list of their maildrops, which will be
+ treated as <a href="http://www.faqs.org/rfcs/rfc822.html">RFC822</a> addresses to which the message will be
delivered.
<b>SEE ALSO</b>
<a href="LDAP_README.html">LDAP_README</a>, Postfix LDAP client guide
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>AUTHOR(S)</b>
- Carsten Hoeger, Hery Rakotoarisoa, John Hensley, Keith
- Stevenson, LaMont Jones, Liviu Daia, Manuel Guesdon, Mike
- Mattice, Prabhat K Singh, Sami Haahtinen, Samuel Tardieu,
+ Carsten Hoeger, Hery Rakotoarisoa, John Hensley, Keith
+ Stevenson, LaMont Jones, Liviu Daia, Manuel Guesdon, Mike
+ Mattice, Prabhat K Singh, Sami Haahtinen, Samuel Tardieu,
Victor Duchovni, and many others.
LDAP_TABLE(5)
as the Postfix main.cf file, and can specify the parame-
ters described below.
-<b>ALTERNATIVE CONFIGURATION</b>
+<b>BACKWARDS COMPATIBILITY</b>
For compatibility with other Postfix lookup tables, MySQL
parameters can also be defined in main.cf. In order to do
that, specify as MySQL source a name that doesn't begin
Support for this form will be removed in a future Postfix
version.
+ Postfix 2.2 has enhanced query interfaces for MySQL and
+ PostreSQL, these include features previously available
+ only in the Postfix LDAP client. In the new interface the
+ SQL query is specified via a single <b>query</b> parameter
+ (described in more detail below). When the new <b>query</b>
+ parameter is not specified in the map definition, Postfix
+ reverts to the old interface, with the SQL query con-
+ structed from the <b>select_field</b>, <b>table</b>, <b>where_field</b> and
+ <b>additional_conditions</b> parameters. The old interface will
+ be gradually phased out. To migrate to the new interface
+ set:
+
+ <b>query</b> = SELECT [<i>select</i><b>_</b><i>field</i>]
+ FROM [<i>table</i>]
+ WHERE [<i>where</i><b>_</b><i>field</i>] = '%s'
+ [<i>additional</i><b>_</b><i>conditions</i>]
+
+ Insert the value, not the name, of each legacy parameter.
+ Note that the <b>additional_conditions</b> parameter is optional
+ and if not empty, will always start with <b>AND</b>.
+
<b>LIST MEMBERSHIP</b>
When using SQL to store lists such as $<a href="postconf.5.html#mynetworks">mynetworks</a>, $<a href="postconf.5.html#mydestination">mydes</a>-
<a href="postconf.5.html#mydestination">tination</a>, $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, etc., it
<b>dbname</b> The database name on the servers. Example:
dbname = customer_database
- The following parameters are used to fill in a SELECT
- query template of the form:
- select [<b>select_field</b>] from [<b>table</b>] where
- [<b>where_field</b>] = '$lookup' [<b>additional_conditions</b>]
+ <b>query</b> The SQL query template used to search the database,
+ where <b>%s</b> is a substitute for the address Postfix is
+ trying to resolve, e.g.
+ query = SELECT replacement FROM aliases WHERE
+ mailbox = '%s'
+
+ This parameter supports the following '%' expan-
+ sions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the input key. SQL
+ quoting is used to make sure that the input
+ key does not add unexpected metacharacters.
+
+ <b>%u</b> When the input key is an address of the form
+ user@domain, <b>%u</b> is replaced by the SQL
+ quoted local part of the address. Other-
+ wise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, the
+ query is suppressed and returns no results.
+
+ <b>%d</b> When the input key is an address of the form
+ user@domain, <b>%d</b> is replaced by the SQL
+ quoted domain part of the address. Other-
+ wise, the query is suppressed and returns no
+ results.
+
+ <b>%[SUD]</b> The upper-case equivalents of the above
+ expansions behave in the <b>query</b> parameter
+ identically to their lower-case counter-
+ parts. With the <b>result_format</b> parameter
+ (see below), they expand the input key
+ rather than the result value.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by
+ the corresponding 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>. If the input key
+ is unqualified or does not have enough
+ domain components to satisfy all the speci-
+ fied patterns, the query is suppressed and
+ returns no results.
+
+ The <b>domain</b> parameter described below limits the
+ input keys to addresses in matching domains. When
+ the <b>domain</b> parameter is non-empty, SQL queries for
+ unqualified addresses or addresses in non-matching
+ domains are suppressed and return no results.
+
+ This parameter is available with Postfix 2.2. In
+ prior releases the SQL query was built from the
+ separate parameters: <b>select_field</b>, <b>table</b>,
+ <b>where_field</b> and <b>additional_conditions</b>. The mapping
+ from the old parameters to the equivalent query is:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The '%s' in the <b>WHERE</b> clause expands to the escaped
+ search string. With Postfix 2.2 these legacy
+ parameters are used if the <b>query</b> parameter is not
+ specified.
+
+ NOTE: DO NOT put quotes around the query parameter.
+
+ <b>result_format (default: %s</b>)
+ Format template applied to result attributes. Most
+ commonly used to append (or prepend) text to the
+ result. This parameter supports the following '%'
+ expansions:
+
+ <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 behaviour is
+ identical to that described with <b>query</b>, and
+ in fact because the input key is known in
+ advance, queries whose key does not contain
+ all the information specified in the result
+ template are suppressed 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 and parame-
+ ter explained below allows one to restrict the num-
+ ber of values in the result, which is especially
+ useful for maps that must return at most one value.
+
+ The default value <b>%s</b> specifies that each result
+ value should be used as is.
+
+ This parameter is available with Postfix 2.2 and
+ later.
+
+ NOTE: DO NOT put quotes around the result format!
+
+ <b>domain (default: no domain list)</b>
+ This is a list of domain names, paths to files, or
+ dictionaries. 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 MySQL server.
+ domain = postfix.org, hash:/etc/postfix/search-
+ domains
+
+ It is best not to use SQL to store the domains eli-
+ gible for SQL lookups.
+
+ This parameter is available with Postfix 2.2 and
+ later.
+
+ NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a>
+ aliases, because the input keys are always unquali-
+ fied.
+
+ <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.
+
+ The following parameters can be used to fill in a SELECT
+ template statement of the form:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The specifier %s is replaced by the search string, and is
+ escaped so if it contains single quotes or other odd char-
+ acters, it will not cause a parse error, or worse, a secu-
+ rity problem.
- $lookup contains the search string, and is escaped so if
- it contains single quotes or other odd characters, it will
- not cause a parse error, or worse, a security problem.
+ As of Postfix 2.2 this interface is obsolete, it is
+ replaced by the more general <b>query</b> interface described
+ above. If the <b>query</b> parameter is defined, the legacy
+ parameters are ignored. Please migrate to the new inter-
+ face as the legacy interface may be removed in a future
+ release.
<b>select_field</b>
The SQL "select" parameter. Example:
- select_field = forw_addr
+ <b>select_field</b> = forw_addr
<b>table</b> The SQL "select .. from" table name. Example:
- table = mxaliases
+ <b>table</b> = mxaliases
<b>where_field</b>
The SQL "select .. where" parameter. Example:
- where_field = alias
+ <b>where_field</b> = alias
<b>additional_conditions</b>
Additional conditions to the SQL query. Example:
- additional_conditions = and status = 'paid'
+ <b>additional_conditions</b> = AND status = 'paid'
<b>SEE ALSO</b>
<a href="postmap.1.html">postmap(1)</a>, Postfix lookup table maintenance
<a href="MYSQL_README.html">MYSQL_README</a>, Postfix MYSQL client guide
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>HISTORY</b>
as the Postfix main.cf file, and can specify the parame-
ters described below.
-<b>ALTERNATIVE CONFIGURATION</b>
+<b>BACKWARDS COMPATIBILITY</b>
For compatibility with other Postfix lookup tables, Post-
greSQL parameters can also be defined in main.cf. In
order to do that, specify as PostgreSQL source a name that
readable. Support for this form will be removed in a
future Postfix version.
+ Postfix 2.2 has enhanced query interfaces for MySQL and
+ PostgreSQL, these include features previously available
+ only in the Postfix LDAP client. In the new interface the
+ SQL query is specified via a single <b>query</b> parameter
+ (described in more detail below). In Postfix 2.1 the
+ parameter precedence was, from highest to lowest,
+ <b>select_function</b>, <b>query</b> and finally <b>select_field</b>, ...
+
+ With Postfix 2.2 the <b>query</b> parameter has highest prece-
+ dence, and is used in preference to the still supported,
+ but slated to be phased out, <b>select_function</b>,
+ <b>select_field</b>, <b>table</b>, <b>where_field</b> and <b>additional_conditions</b>
+ parameters. To migrate to the new interface set:
+
+ <b>query</b> = SELECT <i>select</i><b>_</b><i>function</i>('%s')
+
+ or in the absense of <b>selection_function</b>, the lower prece-
+ dence:
+
+ <b>query</b> = SELECT <i>select</i><b>_</b><i>field</i>
+ FROM <i>table</i>
+ WHERE <i>where</i><b>_</b><i>field</i> = '%s'
+ <i>additional</i><b>_</b><i>conditions</i>
+
+ Use the value, not the name, of each legacy parameter.
+ Note that the <b>additional_conditions</b> parameter is optional
+ and if not empty, will always start with <b>AND</b>.
+
<b>LIST MEMBERSHIP</b>
When using SQL to store lists such as $<a href="postconf.5.html#mynetworks">mynetworks</a>, $<a href="postconf.5.html#mydestination">mydes</a>-
- <a href="postconf.5.html#mydestination">tination</a>, $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, etc., it
- is important to understand that the table must store each
- list member as a separate key. The table lookup verifies
- the *existence* of the key. See "Postfix lists versus
- tables" in the <a href="DATABASE_README.html">DATABASE_README</a> document for a discussion.
-
- Do NOT create tables that return the full list of domains
- in $<a href="postconf.5.html#mydestination">mydestination</a> or $<a href="postconf.5.html#relay_domains">relay_domains</a> etc., or IP addresses
+ <a href="postconf.5.html#mydestination">tination</a>, $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, etc., it
+ is important to understand that the table must store each
+ list member as a separate key. The table lookup verifies
+ the *existence* of the key. See "Postfix lists versus
+ tables" in the <a href="DATABASE_README.html">DATABASE_README</a> document for a discussion.
+
+ Do NOT create tables that return the full list of domains
+ in $<a href="postconf.5.html#mydestination">mydestination</a> or $<a href="postconf.5.html#relay_domains">relay_domains</a> etc., or IP addresses
in $<a href="postconf.5.html#mynetworks">mynetworks</a>.
DO create tables with each matching item as a key and with
- an arbitrary value. With SQL databases it is not uncommon
+ an arbitrary value. With SQL databases it is not uncommon
to return the key itself or a constant value.
<b>PGSQL PARAMETERS</b>
- <b>hosts</b> The hosts that Postfix will try to connect to and
+ <b>hosts</b> The hosts that Postfix will try to connect to and
query from. Specify <i>unix:</i> for UNIX-domain sockets,
<i>inet:</i> for TCP connections (default). Example:
hosts = host1.some.domain host2.some.domain
hosts = unix:/file/name
- The hosts are tried in random order, with all con-
+ The hosts are tried in random order, with all con-
nections over UNIX domain sockets being tried
- before those over TCP. The connections are auto-
- matically closed after being idle for about 1
+ before those over TCP. The connections are auto-
+ matically closed after being idle for about 1
minute, and are re-opened as necessary.
NOTE: the <i>unix:</i> and <i>inet:</i> prefixes are accepted for
- backwards compatibility reasons, but are actually
+ backwards compatibility reasons, but are actually
ignored. The PostgreSQL client library will always
try to connect to an UNIX socket if the name starts
- with a slash, and will try a TCP connection other-
+ with a slash, and will try a TCP connection other-
wise.
<b>user, password</b>
- The user name and password to log into the pgsql
+ The user name and password to log into the pgsql
server. Example:
user = someone
password = some_password
<b>dbname</b> The database name on the servers. Example:
dbname = customer_database
- The following parameters can be used to fill in a SELECT
- template statement of the form:
- select [<b>select_field</b>] from [<b>table</b>] where
- [<b>where_field</b>] = '$lookup' [<b>additional_conditions</b>]
-
- $lookup contains the search string, and is escaped so if
- it contains single quotes or other odd characters, it will
- not cause a parse error, or worse, a security problem.
-
- <b>select_field</b>
- The SQL "select" parameter. Example:
- select_field = forw_addr
-
- <b>table</b> The SQL "select .. from" table name. Example:
- table = mxaliases
-
- <b>where_field</b>
- The SQL "select .. where" parameter. Example:
- where_field = alias
-
- <b>additional_conditions</b>
- Additional conditions to the SQL query. Example:
- additional_conditions = and status = 'paid'
-
- The following parameters provide ways to override the
- default SELECT statement. Setting them will instruct
- Postfix to ignore the above <b>table</b>, <b>select_field</b>,
- <b>where_field</b> and <b>additional_conditions</b> parameters:
+ <b>query</b> The SQL query template used to search the database,
+ where <b>%s</b> is a substitute for the address Postfix is
+ trying to resolve, e.g.
+ query = SELECT replacement FROM aliases WHERE
+ mailbox = '%s'
- <b>query</b> This parameter specifies a complete SQL query.
- Example:
- query = select forw_addr from mxaliases where
- alias = '%s' and status = 'paid'
-
- This parameter supports the following '%' expan-
+ This parameter supports the following '%' expan-
sions:
- <b>%s</b> This is replaced by the input key. Quoting
- is used to make sure that the input key does
- not add unexpected metacharacters.
+ <b>%%</b> This is replaced by a literal '%' character.
+ (Postfix 2.2 and later)
+
+ <b>%s</b> This is replaced by the input key. SQL
+ quoting is used to make sure that the input
+ key does not add unexpected metacharacters.
<b>%u</b> When the input key is an address of the form
- user@domain, <b>%u</b> is replaced by the quoted
- local part of the address. If no domain is
- specified, <b>%u</b> is replaced by the entire
- search string.
+ user@domain, <b>%u</b> is replaced by the SQL
+ quoted local part of the address. Other-
+ wise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, the
+ query is suppressed and returns no results.
<b>%d</b> When the input key is an address of the form
- user@domain, <b>%d</b> is replaced by the quoted
- domain part of the address. When the input
- key has no domain qualifier, <b>%d</b> is replaced
- by the entire search string.
+ user@domain, <b>%d</b> is replaced by the SQL
+ quoted domain part of the address. Other-
+ wise, the query is suppressed and returns no
+ results.
+
+ <b>%[SUD]</b> The upper-case equivalents of the above
+ expansions behave in the <b>query</b> parameter
+ identically to their lower-case counter-
+ parts. With the <b>result_format</b> parameter
+ (see below), they expand the input key
+ rather than the result value.
+
+ The above %S, %U and %D expansions are
+ available with Postfix 2.2 and later
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by
+ the corresponding 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>. If the input key
+ is unqualified or does not have enough
+ domain components to satisfy all the speci-
+ fied patterns, the query is suppressed and
+ returns no results.
+
+ The above %1, ... %9 expansions are avail-
+ able with Postfix 2.2 and later
+
+ The <b>domain</b> parameter described below limits the
+ input keys to addresses in matching domains. When
+ the <b>domain</b> parameter is non-empty, SQL queries for
+ unqualified addresses or addresses in non-matching
+ domains are suppressed and return no results.
+
+ The precedence of this parameter has changed with
+ Postfix 2.2, in prior releases the precedence was,
+ from highest to lowest, <b>select_function</b>, <b>query</b>,
+ <b>select_field</b>, ...
+
+ With Postfix 2.2 the <b>query</b> parameter has highest
+ precedence, see COMPATIBILITY above.
+
+ NOTE: DO NOT put quotes around the <b>query</b> parameter.
+
+ <b>result_format (default: %s</b>)
+ Format template applied to result attributes. Most
+ commonly used to append (or prepend) text to the
+ result. This parameter supports the following '%'
+ expansions:
+
+ <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 behaviour is
+ identical to that described with <b>query</b>, and
+ in fact because the input key is known in
+ advance, queries whose key does not contain
+ all the information specified in the result
+ template are suppressed 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 and parame-
+ ter explained below allows one to restrict the num-
+ ber of values in the result, which is especially
+ useful for maps that must return at most one value.
+
+ The default value <b>%s</b> specifies that each result
+ value should be used as is.
+
+ This parameter is available with Postfix 2.2 and
+ later.
+
+ NOTE: DO NOT put quotes around the result format!
+
+ <b>domain (default: no domain list)</b>
+ This is a list of domain names, paths to files, or
+ dictionaries. 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 PostgreSQL server.
+ domain = postfix.org, hash:/etc/postfix/search-
+ domains
+
+ It is best not to use SQL to store the domains eli-
+ gible for SQL lookups.
+
+ This parameter is available with Postfix 2.2 and
+ later.
+
+ NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a>
+ aliases, because the input keys are always unquali-
+ fied.
+
+ <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.
+
+ Pre-Postfix 2.2 legacy interfaces:
<b>select_function</b>
This parameter specifies a database function name.
select_function = my_lookup_user_alias
This is equivalent to:
- query = select my_lookup_user_alias('%s')
+ query = SELECT my_lookup_user_alias('%s')
+
+ This parameter overrides the legacy table-related
+ fields (described below). With Postfix versions
+ prior to 2.2, it also overrides the <b>query</b> parame-
+ ter. Starting with Postfix 2.2, the <b>query</b> parameter
+ has highest precedence, and this parameter is dep-
+ recated. Please migrate to the new <b>query</b> interface
+ as this interface is slated to be phased out.
+
+ The following parameters (with lower precedence than the
+ <b>select_function</b> interface described above) can be used to
+ build the SQL select statement as follows:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The specifier %s is replaced with each lookup by the
+ lookup key and is escaped so if it contains single quotes
+ or other odd characters, it will not cause a parse error,
+ or worse, a security problem.
+
+ Starting with Postfix 2.2, this interface is obsoleted by
+ the more general <b>query</b> interface described above. If
+ higher precedence the <b>query</b> or <b>select_function</b> parameters
+ described above are defined, these parameters are ignored.
+ Please migrate to the new <b>query</b> interface as this inter-
+ face is slated to be phased out.
- and overrides both the <b>query</b> parameter and the ta-
- ble-related fields above.
+ <b>select_field</b>
+ The SQL "select" parameter. Example:
+ <b>select_field</b> = forw_addr
- As of June 2002, if the function returns a single
- row and a single column AND that value is NULL,
- then the result will be treated as if the key was
- not in the dictionary.
+ <b>table</b> The SQL "select .. from" table name. Example:
+ <b>table</b> = mxaliases
- Future versions will allow functions to return
- result sets.
+ <b>where_field</b>
+ The SQL "select .. where" parameter. Example:
+ <b>where_field</b> = alias
+
+ <b>additional_conditions</b>
+ Additional conditions to the SQL query. Example:
+ <b>additional_conditions</b> = AND status = 'paid'
<b>SEE ALSO</b>
<a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
<a href="PGSQL_README.html">PGSQL_README</a>, Postfix PostgreSQL client guide
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>HISTORY</b>
This is enabled by default.
</p>
+<p> Note: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
+
<p>
Example:
</p>
</p>
<p>
-This feature is enabled by default and must not be turned off.
+Note 1: This feature is enabled by default and must not be turned off.
Postfix does not support domain-less addresses.
</p>
+<p> Note 2: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
+
</DD>
</p>
<p>
-This feature is enabled by default. If disabled, users will not be
+Note 1: This feature is enabled by default. If disabled, users will not be
able to send mail to "user@partialdomainname" but will have to
specify full domain names instead.
</p>
+<p> Note 2: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
+
</DD>
to eliminate the delay.
</p>
+<p> Note: with Postfix version 2.2, message header address mapping
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
+
<p>
Examples:
</p>
<p> The Internet protocols Postfix will attempt to use when making
or accepting connections. Specify one or more of "ipv4"
or "ipv6", separated by whitespace or commas. The form
-"all" is equivalent to "ipv4, ipv6". </p>
+"all" is equivalent to "ipv4, ipv6" or "ipv4", depending
+on whether the operating system implements IPv6. </p>
<p> This feature is available in Postfix version 2.2 and later. </p>
but strips "user@any.thing.else.example.com" to "user@example.com".
</p>
+<p> Note: with Postfix version 2.2, message header address masquerading
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
+
<p>
Example:
</p>
enabled by default.
</p>
+<p> Note: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = static:all". </p>
+
<p>
Example:
</p>
Note: with this form, the passwords for the LDAP sources are
written in main.cf, which is normally world-readable. Support
for this form will be removed in a future Postfix version.
+
+Postfix 2.2 has enhanced query interfaces for MySQL and PostgreSQL,
+these now include features previously available only in the
+Postfix LDAP client. This work also created an opportunity for
+improvements in the LDAP interface. The primary compatibility
+issue is that \fBresult_filter\fR (a name that has caused some
+confusion as to its meaning in the past) has been renamed to
+\fBresult_format\fR. For backwards compatibility with the pre
+2.2 LDAP client, \fBresult_filter\fR can for now be used instead
+of \fBresult_format\fR, when the latter parameter is not also set.
+The new name better reflects the function of the parameter. This
+compatibility interface may be removed in a future release.
.SH "LIST MEMBERSHIP"
.na
.nf
The port the LDAP server listens on, e.g.
.ti +4
server_port = 778
-.IP "\fBsearch_base (No default; you must configure this)\fR"
-The RFC2253 base DN at which to conduct the search, e.g.
-.ti +4
-search_base = dc=your, dc=com
.IP "\fBtimeout (default: 10 seconds)\fR"
The number of seconds a search can take before timing out, e.g.
.ti +4
timeout = 5
+.IP "\fBsearch_base (No default; you must configure this)\fR"
+The RFC2253 base DN at which to conduct the search, e.g.
+.ti +4
+search_base = dc=your, dc=com
+.IP
+With Postfix 2.2 and later this parameter supports the
+following '%' expansions:
+.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character.
+.IP "\fB\fB%s\fR\fR"
+This is replaced by the input key.
+RFC 2253 quoting is used to make sure that the input key
+does not add unexpected metacharacters.
+.IP "\fB\fB%u\fR\fR"
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the (RFC 2253) quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the search is suppressed and returns
+no results.
+.IP "\fB\fB%d\fR\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the (RFC 2253) quoted domain part of the address.
+Otherwise, the search is suppressed and returns no results.
+.IP "\fB\fB%[SUD]\fR\fR"
+For the \fBsearch_base\fR parameter, the upper-case equivalents
+of the above expansions behave identically to their lower-case
+counter-parts. With the \fBresult_format\fR parameter (previously
+called \fBresult_filter\fR see the COMPATIBILITY section and below),
+they expand to the corresponding components of input key rather
+than the result value.
+.IP "\fB\fB%[1-9]\fR\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. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the search is suppressed and returns
+no results.
+.RE
.IP "\fBquery_filter (default: mailacceptinggeneralid=%s)\fR"
The RFC2254 filter used to search the directory, where \fB%s\fR
is a substitute for the address Postfix is trying to resolve,
This parameter supports the following '%' expansions:
.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character. (Postfix 2.2 and later).
.IP "\fB\fB%s\fR\fR"
-This is replaced by the input key. RFC 2254 quoting is used
-to make sure that the input key does not add unexpected
-metacharacters.
+This is replaced by the input key.
+RFC 2254 quoting is used to make sure that the input key
+does not add unexpected metacharacters.
.IP "\fB\fB%u\fR\fR"
-When the input key is an address of the form user@domain,
-\fB%u\fR is replaced by the (RFC 2254) quoted local part of the
-address. Otherwise, \fB%u\fR is replaced by the entire
-search string.
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the (RFC 2254) quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the search is suppressed and returns
+no results.
.IP "\fB\fB%d\fR\fR"
-When the input key is an address of the form user@domain,
-\fB%d\fR is replaced by the (RFC 2254) quoted domain part of the
-address. Otherwise, \fB%d\fR is replaced by the entire
-search string.
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the (RFC 2254) quoted domain part of the address.
+Otherwise, the search is suppressed and returns no results.
+.IP "\fB\fB%[SUD]\fR\fR"
+The upper-case equivalents of the above expansions behave in the
+\fBquery_filter\fR parameter identically to their lower-case
+counter-parts. With the \fBresult_format\fR parameter (previously
+called \fBresult_filter\fR see the COMPATIBILITY section and below),
+they expand to the corresponding components of input key rather
+than the result value.
+.IP
+The above %S, %U and %D expansions are available with Postfix 2.2
+and later.
+.IP "\fB\fB%[1-9]\fR\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. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the saerch is suppressed and returns
+no results.
+.IP
+The above %1, ..., %9 expansions are available with Postfix 2.2
+and later.
.RE
.IP
The "domain" parameter described below limits the input
addresses or addresses in non-matching domains are suppressed
and return no results.
-NOTE: DO NOT put quotes around the query filter.
-.IP "\fBresult_filter (default: \fB%s\fR)\fR"
-Format template applied to result attributes. Supports the
-same expansions as the query_filter, and can be easily used
-to append (or prepend) text. This parameter supports the
-following '%' expansions:
+NOTE: DO NOT put quotes around the \fBquery_filter\fR parameter.
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Called \fBresult_filter\fR in Postfix releases prior to 2.2.
+Format template applied to result attributes. Most commonly used
+to append (or prepend) text to the result. This parameter supports
+the following '%' expansions:
.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character. (Postfix 2.2 and later).
.IP "\fB\fB%s\fR\fR"
-This is replaced by the value of the result attribute.
+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. Otherwise, \fB%u\fR is replaced by the entire
-attribute value.
+address. When the result has an empty localpart it is skipped.
.IP "\fB\fB%d\fR\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. Otherwise, \fB%d\fR is replaced by
-the entire attribute value.
+the attribute value. When the result is unqualified it
+is skipped.
+.IP "\fB\fB%[SUD1-9]\fR\fB"
+The upper-case and decimal digit expansions interpolate
+the parts of the input key rather than the result. Their
+behaviour 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.
+.IP
+The above %S, %U, %D and %1, ..., %9 expansions are available with
+Postfix 2.2 and later.
.RE
.IP
-For example, using "result_filter = smtp:[%s]" allows one
+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 filter, multiple values
+table. After applying the result format, multiple values
are concatenated as comma separated strings. The expansion_limit
and size_limit parameters explained below allow one to
restrict the number of values in the result, which is
The default value \fB%s\fR specifies that each
attribute value should be used as is.
-NOTE: DO NOT put quotes around the result filter!
+This parameter was called \fBresult_filter\fR in Postfix
+releases prior to 2.2. If no "result_format" is specified,
+the value of "result_filter" will be used instead before
+resorting to the default value. This provides compatibility
+with old configuration files.
+
+NOTE: DO NOT put quotes around the result format!
.IP "\fBdomain (default: no domain list)\fR"
This is a list of domain names, paths to files, or
dictionaries. When specified, only fully qualified search
entries returned by the lookup, to be resolved to an email
address.
.ti +4
-result_attribute = mailbox,maildrop
+result_attribute = mailbox, maildrop
.IP "\fBspecial_result_attribute (No default)\fR"
The attribute(s) of directory entries that can contain DNs
or URLs. If found, a recursive subsequent search is done
values.
.IP "\fBsize_limit (default: $expansion_limit)\fR"
A limit on the number of LDAP entries returned by any single
-LDAP query performed as part of the lookup. A setting of
+LDAP search performed as part of the lookup. A setting of
0 disables the limit. Expansion of DN and URL references
involves nested LDAP queries, each of which is separately
subjected to this limit.
Note: even a single LDAP entry can generate multiple lookup
results, via multiple result attributes and/or multi-valued
-result attributes. This limit caps the per query resource
+result attributes. This limit caps the per search resource
utilization on the LDAP server, not the final multiplicity
of the lookup result. It is analogous to the "-z" option
of "ldapsearch".
The file /etc/postfix/mysql-aliases.cf has the same format as
the Postfix main.cf file, and can specify the parameters
described below.
-.SH "ALTERNATIVE CONFIGURATION"
+.SH "BACKWARDS COMPATIBILITY"
.na
.nf
.ad
Note: with this form, the passwords for the MySQL sources are
written in main.cf, which is normally world-readable. Support
for this form will be removed in a future Postfix version.
+
+Postfix 2.2 has enhanced query interfaces for MySQL and PostreSQL,
+these include features previously available only in the Postfix
+LDAP client. In the new interface the SQL query is specified via
+a single \fBquery\fR parameter (described in more detail below).
+When the new \fBquery\fR parameter is not specified in the map
+definition, Postfix reverts to the old interface, with the SQL
+query constructed from the \fBselect_field\fR, \fBtable\fR,
+\fBwhere_field\fR and \fBadditional_conditions\fR parameters.
+The old interface will be gradually phased out. To migrate to
+the new interface set:
+
+.ti +4
+\fBquery\fR = SELECT [\fIselect_field\fR]
+.ti +8
+FROM [\fItable\fR]
+.ti +8
+WHERE [\fIwhere_field\fR] = '%s'
+.ti +12
+[\fIadditional_conditions\fR]
+
+Insert the value, not the name, of each legacy parameter. Note
+that the \fBadditional_conditions\fR parameter is optional
+and if not empty, will always start with \fBAND\fR.
.SH "LIST MEMBERSHIP"
.na
.nf
The database name on the servers. Example:
.ti +4
dbname = customer_database
+.IP "\fBquery\fR"
+The SQL query template used to search the database, where \fB%s\fR
+is a substitute for the address Postfix is trying to resolve,
+e.g.
+.ti +4
+query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character.
+.IP "\fB\fB%s\fR\fR"
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
+.IP "\fB\fB%u\fR\fR"
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the query is suppressed and returns
+no results.
+.IP "\fB\fB%d\fR\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the query is suppressed and returns no results.
+.IP "\fB\fB%[SUD]\fR\fR"
+The upper-case equivalents of the above expansions behave in the
+\fBquery\fR parameter identically to their lower-case counter-parts.
+With the \fBresult_format\fR parameter (see below), they expand the
+input key rather than the result value.
+.IP "\fB\fB%[1-9]\fR\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. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the query is suppressed and returns
+no results.
+.RE
+.IP
+The \fBdomain\fR parameter described below limits the input
+keys to addresses in matching domains. When the \fBdomain\fR
+parameter is non-empty, SQL queries for unqualified addresses
+or addresses in non-matching domains are suppressed
+and return no results.
+
+This parameter is available with Postfix 2.2. In prior releases
+the SQL query was built from the separate parameters:
+\fBselect_field\fR, \fBtable\fR, \fBwhere_field\fR and
+\fBadditional_conditions\fR. The mapping from the old parameters
+to the equivalent query is:
+
+.ti +4
+SELECT [\fBselect_field\fR]
+.ti +4
+FROM [\fBtable\fR]
+.ti +4
+WHERE [\fBwhere_field\fR] = '%s'
+.ti +10
+[\fBadditional_conditions\fR]
+
+The '%s' in the \fBWHERE\fR clause expands to the escaped search string.
+With Postfix 2.2 these legacy parameters are used if the \fBquery\fR
+parameter is not specified.
+
+NOTE: DO NOT put quotes around the query parameter.
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Format template applied to result attributes. Most commonly used
+to append (or prepend) text to the result. This parameter supports
+the following '%' expansions:
+.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character.
+.IP "\fB\fB%s\fR\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\fB%d\fR\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\fB%[SUD1-9]\fR\fB"
+The upper-case and decimal digit expansions interpolate
+the parts of the input key rather than the result. Their
+behaviour is identical to that described with \fBquery\fR,
+and in fact because the input key is known in advance, queries
+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
+and parameter explained below allows one to restrict the number
+of values in the result, which is especially useful for maps that
+must return at most one value.
+
+The default value \fB%s\fR specifies that each result value should
+be used as is.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT put quotes around the result format!
+.IP "\fBdomain (default: no domain list)\fR"
+This is a list of domain names, paths to files, or
+dictionaries. 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 MySQL server.
+.ti +4
+domain = postfix.org, hash:/etc/postfix/searchdomains
+
+It is best not to use SQL to store the domains eligible
+for SQL lookups.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT define this parameter for local(8) aliases,
+because the input keys are always unqualified.
+.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.
.PP
-The following parameters are used to fill in a SELECT
-query template of the form:
+The following parameters can be used to fill in a
+SELECT template statement of the form:
+
.ti +4
-select [\fBselect_field\fR] from [\fBtable\fR] where
-.ti +8
-[\fBwhere_field\fR] = '$lookup' [\fBadditional_conditions\fR]
+SELECT [\fBselect_field\fR]
+.ti +4
+FROM [\fBtable\fR]
+.ti +4
+WHERE [\fBwhere_field\fR] = '%s'
+.ti +10
+[\fBadditional_conditions\fR]
+
+The specifier %s is replaced by the search string, and is
+escaped so if it contains single quotes or other odd characters,
+it will not cause a parse error, or worse, a security problem.
-$lookup contains the search string, and is escaped so if
-it contains single quotes or other odd characters, it will
-not cause a parse error, or worse, a security problem.
+As of Postfix 2.2 this interface is obsolete, it is replaced
+by the more general \fBquery\fR interface described above.
+If the \fBquery\fR parameter is defined, the legacy parameters
+are ignored. Please migrate to the new interface as the legacy
+interface may be removed in a future release.
.IP "\fBselect_field\fR"
The SQL "select" parameter. Example:
.ti +4
-select_field = forw_addr
+\fBselect_field\fR = forw_addr
.IP "\fBtable\fR"
The SQL "select .. from" table name. Example:
.ti +4
-table = mxaliases
+\fBtable\fR = mxaliases
.IP "\fBwhere_field\fR
The SQL "select .. where" parameter. Example:
.ti +4
-where_field = alias
+\fBwhere_field\fR = alias
.IP "\fBadditional_conditions\fR
Additional conditions to the SQL query. Example:
.ti +4
-additional_conditions = and status = 'paid'
+\fBadditional_conditions\fR = AND status = 'paid'
.SH "SEE ALSO"
.na
.nf
The file /etc/postfix/pgsql-aliases.cf has the same format as
the Postfix main.cf file, and can specify the parameters
described below.
-.SH "ALTERNATIVE CONFIGURATION"
+.SH "BACKWARDS COMPATIBILITY"
.na
.nf
.ad
are written in main.cf, which is normally world-readable.
Support for this form will be removed in a future Postfix
version.
+
+Postfix 2.2 has enhanced query interfaces for MySQL and PostgreSQL,
+these include features previously available only in the Postfix
+LDAP client. In the new interface the SQL query is specified via
+a single \fBquery\fR parameter (described in more detail below).
+In Postfix 2.1 the parameter precedence was, from highest to lowest,
+\fBselect_function\fR, \fBquery\fR and finally \fBselect_field\fR, ...
+
+With Postfix 2.2 the \fBquery\fR parameter has highest precedence,
+and is used in preference to the still supported, but slated to be
+phased out, \fBselect_function\fR, \fBselect_field\fR, \fBtable\fR,
+\fBwhere_field\fR and \fBadditional_conditions\fR parameters. To
+migrate to the new interface set:
+
+.ti +4
+\fBquery\fR = SELECT \fIselect_function\fR('%s')
+
+or in the absense of \fBselection_function\fR, the lower precedence:
+
+.ti +4
+\fBquery\fR = SELECT \fIselect_field\fR
+.ti +8
+FROM \fItable\fR
+.ti +8
+WHERE \fIwhere_field\fR = '%s'
+.ti +12
+\fIadditional_conditions\fR
+
+Use the value, not the name, of each legacy parameter. Note
+that the \fBadditional_conditions\fR parameter is optional
+and if not empty, will always start with \fBAND\fR.
.SH "LIST MEMBERSHIP"
.na
.nf
The database name on the servers. Example:
.ti +4
dbname = customer_database
-.PP
-The following parameters can be used to fill in a SELECT
-template statement of the form:
-.ti +4
-select [\fBselect_field\fR] from [\fBtable\fR] where
-.ti +8
-[\fBwhere_field\fR] = '$lookup' [\fBadditional_conditions\fR]
-
-$lookup contains the search string, and is escaped so if
-it contains single quotes or other odd characters, it will
-not cause a parse error, or worse, a security problem.
-.IP "\fBselect_field\fR"
-The SQL "select" parameter. Example:
-.ti +4
-select_field = forw_addr
-.IP "\fBtable\fR"
-The SQL "select .. from" table name. Example:
-.ti +4
-table = mxaliases
-.IP "\fBwhere_field\fR
-The SQL "select .. where" parameter. Example:
-.ti +4
-where_field = alias
-.IP "\fBadditional_conditions\fR
-Additional conditions to the SQL query. Example:
-.ti +4
-additional_conditions = and status = 'paid'
-.PP
-The following parameters provide ways to override the default
-SELECT statement. Setting them will instruct Postfix to ignore
-the above \fBtable\fR, \fBselect_field\fR, \fBwhere_field\fR and
-\fBadditional_conditions\fR parameters:
.IP "\fBquery\fR"
-This parameter specifies a complete SQL query. Example:
+The SQL query template used to search the database, where \fB%s\fR
+is a substitute for the address Postfix is trying to resolve,
+e.g.
.ti +4
-query = select forw_addr from mxaliases where
-.ti +8
-alias = '%s' and status = 'paid'
+query = SELECT replacement FROM aliases WHERE mailbox = '%s'
This parameter supports the following '%' expansions:
.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character. (Postfix 2.2 and later)
.IP "\fB\fB%s\fR\fR"
-This is replaced by the input key. Quoting is used to make sure
-that the input key does not add unexpected metacharacters.
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
.IP "\fB\fB%u\fR\fR"
-When the input key is an address of the form user@domain,
-\fB%u\fR is replaced by the quoted local part of the address.
-If no domain is specified, \fB%u\fR is replaced by the entire
-search string.
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the query is suppressed and returns
+no results.
+.IP "\fB\fB%d\fR\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the query is suppressed and returns no results.
+.IP "\fB\fB%[SUD]\fR\fR"
+The upper-case equivalents of the above expansions behave in the
+\fBquery\fR parameter identically to their lower-case counter-parts.
+With the \fBresult_format\fR parameter (see below), they expand the
+input key rather than the result value.
+.IP
+The above %S, %U and %D expansions are available with Postfix 2.2
+and later
+.IP "\fB\fB%[1-9]\fR\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. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the query is suppressed and returns
+no results.
+.IP
+The above %1, ... %9 expansions are available with Postfix 2.2
+and later
+.RE
+.IP
+The \fBdomain\fR parameter described below limits the input
+keys to addresses in matching domains. When the \fBdomain\fR
+parameter is non-empty, SQL queries for unqualified addresses
+or addresses in non-matching domains are suppressed
+and return no results.
+
+The precedence of this parameter has changed with Postfix 2.2,
+in prior releases the precedence was, from highest to lowest,
+\fBselect_function\fR, \fBquery\fR, \fBselect_field\fR, ...
+
+With Postfix 2.2 the \fBquery\fR parameter has highest precedence,
+see COMPATIBILITY above.
+
+NOTE: DO NOT put quotes around the \fBquery\fR parameter.
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Format template applied to result attributes. Most commonly used
+to append (or prepend) text to the result. This parameter supports
+the following '%' expansions:
+.RS
+.IP "\fB\fB%%\fR\fR"
+This is replaced by a literal '%' character.
+.IP "\fB\fB%s\fR\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\fB%d\fR\fR"
-When the input key is an address of the form user@domain,
-\fB%d\fR is replaced by the quoted domain part of the address.
-When the input key has no domain qualifier, \fB%d\fR is replaced
-by the entire search string.
+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\fB%[SUD1-9]\fR\fB"
+The upper-case and decimal digit expansions interpolate
+the parts of the input key rather than the result. Their
+behaviour is identical to that described with \fBquery\fR,
+and in fact because the input key is known in advance, queries
+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
+and parameter explained below allows one to restrict the number
+of values in the result, which is especially useful for maps that
+must return at most one value.
+
+The default value \fB%s\fR specifies that each result value should
+be used as is.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT put quotes around the result format!
+.IP "\fBdomain (default: no domain list)\fR"
+This is a list of domain names, paths to files, or
+dictionaries. 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 PostgreSQL server.
+.ti +4
+domain = postfix.org, hash:/etc/postfix/searchdomains
+
+It is best not to use SQL to store the domains eligible
+for SQL lookups.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT define this parameter for local(8) aliases,
+because the input keys are always unqualified.
+.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.
+.PP
+Pre-Postfix 2.2 legacy interfaces:
.IP "\fBselect_function\fR"
This parameter specifies a database function name. Example:
.ti +4
This is equivalent to:
.ti +4
-query = select my_lookup_user_alias('%s')
+query = SELECT my_lookup_user_alias('%s')
+
+This parameter overrides the legacy table-related fields (described
+below). With Postfix versions prior to 2.2, it also overrides the
+\fBquery\fR parameter. Starting with Postfix 2.2, the \fBquery\fR
+parameter has highest precedence, and this parameter is deprecated.
+Please migrate to the new \fBquery\fR interface as this interface
+is slated to be phased out.
+.PP
+The following parameters (with lower precedence than the
+\fBselect_function\fR interface described above) can be used to
+build the SQL select statement as follows:
-and overrides both the \fBquery\fR parameter and the table-related
-fields above.
+.ti +4
+SELECT [\fBselect_field\fR]
+.ti +4
+FROM [\fBtable\fR]
+.ti +4
+WHERE [\fBwhere_field\fR] = '%s'
+.ti +10
+[\fBadditional_conditions\fR]
-As of June 2002, if the function returns a single row and
-a single column AND that value is NULL, then the result
-will be treated as if the key was not in the dictionary.
+The specifier %s is replaced with each lookup by the lookup key
+and is escaped so if it contains single quotes or other odd
+characters, it will not cause a parse error, or worse, a security
+problem.
-Future versions will allow functions to return result sets.
+Starting with Postfix 2.2, this interface is obsoleted by the more
+general \fBquery\fR interface described above. If higher precedence
+the \fBquery\fR or \fBselect_function\fR parameters described above
+are defined, these parameters are ignored. Please migrate to the new
+\fBquery\fR interface as this interface is slated to be phased out.
+.IP "\fBselect_field\fR"
+The SQL "select" parameter. Example:
+.ti +4
+\fBselect_field\fR = forw_addr
+.IP "\fBtable\fR"
+The SQL "select .. from" table name. Example:
+.ti +4
+\fBtable\fR = mxaliases
+.IP "\fBwhere_field\fR
+The SQL "select .. where" parameter. Example:
+.ti +4
+\fBwhere_field\fR = alias
+.IP "\fBadditional_conditions\fR
+Additional conditions to the SQL query. Example:
+.ti +4
+\fBadditional_conditions\fR = AND status = 'paid'
.SH "SEE ALSO"
.na
.nf
Enable the rewriting of the form "user%domain" to "user@domain".
This is enabled by default.
.PP
+Note: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+.PP
+To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
Example:
.PP
.nf
addresses without domain information. With remotely submitted mail,
append the string "@$remote_header_rewrite_domain" instead.
.PP
-This feature is enabled by default and must not be turned off.
+Note 1: This feature is enabled by default and must not be turned off.
Postfix does not support domain-less addresses.
+.PP
+Note 2: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+.PP
+To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
.SH append_dot_mydomain (default: yes)
With locally submitted mail, append the string ".$mydomain" to
addresses that have no ".domain" information. With remotely submitted
mail, append the string ".$remote_header_rewrite_domain"
instead.
.PP
-This feature is enabled by default. If disabled, users will not be
+Note 1: This feature is enabled by default. If disabled, users will not be
able to send mail to "user@partialdomainname" but will have to
specify full domain names instead.
+.PP
+Note 2: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+.PP
+To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
.SH application_event_drain_time (default: 100s)
How long the \fBpostkick\fR(1) command waits for a request to enter the
server's input buffer before giving up.
will become visible after a minute or so. Use "\fBpostfix reload\fR"
to eliminate the delay.
.PP
+Note: with Postfix version 2.2, message header address mapping
+happens only when message header address rewriting is enabled:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+.PP
+To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
Examples:
.PP
.nf
The Internet protocols Postfix will attempt to use when making
or accepting connections. Specify one or more of "ipv4"
or "ipv6", separated by whitespace or commas. The form
-"all" is equivalent to "ipv4, ipv6".
+"all" is equivalent to "ipv4, ipv6" or "ipv4", depending
+on whether the operating system implements IPv6.
.PP
This feature is available in Postfix version 2.2 and later.
.PP
does not change "user@any.thing.foo.example.com" or "user@foo.example.com",
but strips "user@any.thing.else.example.com" to "user@example.com".
.PP
+Note: with Postfix version 2.2, message header address masquerading
+happens only when message header address rewriting is enabled:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+.PP
+To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
Example:
.PP
.nf
necessary if your machine is connected to UUCP networks. It is
enabled by default.
.PP
+Note: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+.PP
+To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
Example:
.PP
.nf
<tr> <td> <a href="#standard"> Rewrite addresses to standard form</a>
</td> <td nowrap> all mail </td> <td> trivial-<br>rewrite(8) </td>
<td> append_at_myorigin, append_dot_mydomain, swap_bangpath,
-allow_percent_hack </td> <td> none </td> </tr>
+allow_percent_hack </td> <td> local_header_rewrite_clients,
+remote_header_rewrite_domain </td> </tr>
-<tr> <td> <a href="#canonical"> Canonical address mapping </a>
-</td> <td nowrap> all mail </td> <td> cleanup(8) </td> <td>
-canonical_maps </td> <td> receive_override_options </td> </tr>
+<tr> <td> <a href="#canonical"> Canonical address mapping </a> </td>
+<td nowrap> all mail </td> <td> cleanup(8) </td> <td> canonical_maps
+</td> <td> receive_override_options, local_header_rewrite_clients,
+remote_header_rewrite_domain </td> </tr>
-<tr> <td> <a href="#canonical"> Address masquerading </a> </td>
-<td nowrap> all mail </td> <td> cleanup(8) </td> <td> masquerade_domains
-</td> <td> receive_override_options </td> </tr>
+<tr> <td> <a href="#masquerade"> Address masquerading </a> </td> <td
+nowrap> all mail </td> <td> cleanup(8) </td> <td> masquerade_domains
+</td> <td> receive_override_options, local_header_rewrite_clients,
+remote_header_rewrite_domain </td> </tr>
<tr> <td> <a href="#auto_bcc"> Automatic BCC recipients </a> </td>
<td nowrap> new mail </td> <td> cleanup(8) </td> <td> always_bcc,
form is to reduce the number of entries needed in lookup tables.
</p>
-<p> Postfix versions 2.2 and later do not rewrite message headers
-from remote SMTP clients at all, unless a non-empty domain name is
-specified with the remote_header_rewrite_domain configuration
-parameter. The local_header_rewrite_clients parameter controls
-what SMTP clients Postfix considers local. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
<p> The Postfix trivial-rewrite(8) daemon implements the following
hard-coded address manipulations: </p>
because a lot of Postfix components expect that all addresses have
the form "user@domain". </p>
-<p> Postfix versions 2.2 and later either do not rewrite message
-headers from remote SMTP clients at all, or they append the domain
-name specified with the remote_header_rewrite_domain configuration
-parameter. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter; otherwise they append the
+domain name specified with the remote_header_rewrite_domain
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "local_header_rewrite_clients =
+static:all". </p>
<p> If your machine is not the main machine for $myorigin and you
wish to have some users delivered locally without going via that
parameter (default: yes). The purpose is to get consistent treatment
of different forms of the same hostname. </p>
-<p> Postfix versions 2.2 and later either do not rewrite message
-headers from remote clients at all, or they append the domain name
-specified with the remote_header_rewrite_domain configuration
-parameter. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter; otherwise they append the
+domain name specified with the remote_header_rewrite_domain
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "local_header_rewrite_clients =
+static:all". </p>
<p> Some will argue that rewriting "host" to "host.domain"
is bad. That is why it can be turned off. Others like the convenience
all header and envelope addresses are rewritten; this is controlled
with the canonical_classes configuration parameter. </p>
-<p> Postfix versions 2.2 and later do not rewrite message headers
-from remote clients at all, unless a non-empty domain name is
-specified with the remote_header_rewrite_domain configuration
-parameter. The local_header_rewrite_clients parameter controls
-what SMTP clients Postfix considers local. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
<p> Address rewriting is
done for local and remote addresses. The mapping is useful to
comes from the gateway itself, instead of from individual machines.
</p>
-<p> Postfix versions 2.2 and later do not rewrite message headers
-from remote SMTP clients at all, unless a non-empty domain name is
-specified with the remote_header_rewrite_domain configuration
-parameter. The local_header_rewrite_clients parameter controls
-what SMTP clients Postfix considers local. </p>
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
<p> Address masquerading is disabled by default, and is implemented
by the cleanup(8) server. To enable, edit the masquerade_domains
make sure the lookup makes sense. In the case of virtual lookups,
maildrops other than mail addresses are pretty useless, because
Postfix can't know how to set the ownership for program or file
- delivery. Your query_filter should probably look something like this: </p>
+ delivery. Your <b>query_filter</b> should probably look something like this: </p>
<blockquote>
<pre>
require some thought on your part to implement safely, considering the
ramifications of this type of delivery. You may decide it's not worth
the bother to allow any of that nonsense in LDAP lookups, ban it in
- the query_filter, and keep things like majordomo lists in local alias
+ the <b>query_filter</b>, and keep things like majordomo lists in local alias
databases. </p>
<blockquote>
<li>Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones:
OpenLDAP cache deprecation. Limits on recursion, expansion
- and query results size. LDAP connection sharing for maps
+ and search results size. LDAP connection sharing for maps
differing only in the query parameters.
<li>Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions in
external files (ldap:/path/ldap.cf) needed to securely store
passwords for plain auth.
+<li>Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.</li>
+
+<li>Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
+
</ul>
And of course Wietse.
# The database name on the servers.
dbname = customer_database
-# The table name.
-table = mxaliases
+# For Postfix 2.2 and later The SQL query template.
+# See mysql_table(5) for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
-# Query components, see below.
+# For Postfix releases prior to 2.2. See mysql_table(5) for details.
select_field = forw_addr
+table = mxaliases
where_field = alias
-
-# You may specify additional_conditions or leave this empty.
-additional_conditions = and status = 'paid'
-
-# The above variables will result in a query of the form:
-#
-# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid'
-#
-# ($lookup is escaped so if it contains single quotes or other odd
-# characters, it will not cause trouble).
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
</pre>
<h2>Additional notes</h2>
<ul>
<li> The initial version was contributed by Scott Cotton and Joshua
-Marcus, IC Group, Inc.
+Marcus, IC Group, Inc.</li>
+
+<li> Liviu Daia revised the configuration interface and added the
+main.cf configuration feature.</li>
-<li>Liviu Daia revised the configuration interface and added the
-main.cf configuration feature.
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
</ul>
# The database name on the servers.
dbname = customer_database
-# The table name.
-table = mxaliases
+# Postfix 2.2 and later The SQL query template. See pgsql_table(5).
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
-# Query components, see below.
+# For Postfix releases prior to 2.2. See pgsql_table(5) for details.
select_field = forw_addr
+table = mxaliases
where_field = alias
-
-# You may specify additional_conditions or leave this empty.
-additional_conditions = and status = 'paid'
-
-# The above variables will result in a query of the form:
-#
-# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid'
-#
-# ($lookup is escaped so if it contains single quotes or other odd
-# characters, it will not cause problems).
-#
-# You may also override the built-in SELECT template. See pgsql_table(5)
-# for details.
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
</pre>
<h2>Using mirrored databases</h2>
<ul>
<li> This code is based upon the Postfix mysql map by Scott Cotton
-and Joshua Marcus, IC Group, Inc.
+and Joshua Marcus, IC Group, Inc.</li>
-<li> The PostgreSQL changes were done by Aaron Sethman.
+<li> The PostgreSQL changes were done by Aaron Sethman.</li>
<li> Updates for Postfix 1.1.x and PostgreSQL 7.1+ and support for
-calling stored procedures were added by Philip Warner.
+calling stored procedures were added by Philip Warner.</li>
-<li> LaMont Jones was the initial Postfix pgsql maintainer.
+<li> LaMont Jones was the initial Postfix pgsql maintainer.</li>
<li> Liviu Daia revised the configuration interface and added the
-main.cf configuration feature.
+main.cf configuration feature.</li>
+
+<li> Liviu Daia revised the configuration interface and added the main.cf
+configuration feature.</li>
+
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
</ul>
# Note: with this form, the passwords for the LDAP sources are
# written in main.cf, which is normally world-readable. Support
# for this form will be removed in a future Postfix version.
+#
+# Postfix 2.2 has enhanced query interfaces for MySQL and PostgreSQL,
+# these now include features previously available only in the
+# Postfix LDAP client. This work also created an opportunity for
+# improvements in the LDAP interface. The primary compatibility
+# issue is that \fBresult_filter\fR (a name that has caused some
+# confusion as to its meaning in the past) has been renamed to
+# \fBresult_format\fR. For backwards compatibility with the pre
+# 2.2 LDAP client, \fBresult_filter\fR can for now be used instead
+# of \fBresult_format\fR, when the latter parameter is not also set.
+# The new name better reflects the function of the parameter. This
+# compatibility interface may be removed in a future release.
# LIST MEMBERSHIP
# .ad
# .fi
# The port the LDAP server listens on, e.g.
# .ti +4
# server_port = 778
-# .IP "\fBsearch_base (No default; you must configure this)\fR"
-# The RFC2253 base DN at which to conduct the search, e.g.
-# .ti +4
-# search_base = dc=your, dc=com
# .IP "\fBtimeout (default: 10 seconds)\fR"
# The number of seconds a search can take before timing out, e.g.
# .ti +4
# timeout = 5
+# .IP "\fBsearch_base (No default; you must configure this)\fR"
+# The RFC2253 base DN at which to conduct the search, e.g.
+# .ti +4
+# search_base = dc=your, dc=com
+# .IP
+# With Postfix 2.2 and later this parameter supports the
+# following '%' expansions:
+# .RS
+# .IP "\fB\fB%%\fR\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB\fB%s\fR\fR"
+# This is replaced by the input key.
+# RFC 2253 quoting is used to make sure that the input key
+# does not add unexpected metacharacters.
+# .IP "\fB\fB%u\fR\fR"
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the (RFC 2253) quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the search is suppressed and returns
+# no results.
+# .IP "\fB\fB%d\fR\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the (RFC 2253) quoted domain part of the address.
+# Otherwise, the search is suppressed and returns no results.
+# .IP "\fB\fB%[SUD]\fR\fR"
+# For the \fBsearch_base\fR parameter, the upper-case equivalents
+# of the above expansions behave identically to their lower-case
+# counter-parts. With the \fBresult_format\fR parameter (previously
+# called \fBresult_filter\fR see the COMPATIBILITY section and below),
+# they expand to the corresponding components of input key rather
+# than the result value.
+# .IP "\fB\fB%[1-9]\fR\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. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the search is suppressed and returns
+# no results.
+# .RE
# .IP "\fBquery_filter (default: mailacceptinggeneralid=%s)\fR"
# The RFC2254 filter used to search the directory, where \fB%s\fR
# is a substitute for the address Postfix is trying to resolve,
#
# This parameter supports the following '%' expansions:
# .RS
+# .IP "\fB\fB%%\fR\fR"
+# This is replaced by a literal '%' character. (Postfix 2.2 and later).
# .IP "\fB\fB%s\fR\fR"
-# This is replaced by the input key. RFC 2254 quoting is used
-# to make sure that the input key does not add unexpected
-# metacharacters.
+# This is replaced by the input key.
+# RFC 2254 quoting is used to make sure that the input key
+# does not add unexpected metacharacters.
# .IP "\fB\fB%u\fR\fR"
-# When the input key is an address of the form user@domain,
-# \fB%u\fR is replaced by the (RFC 2254) quoted local part of the
-# address. Otherwise, \fB%u\fR is replaced by the entire
-# search string.
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the (RFC 2254) quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the search is suppressed and returns
+# no results.
# .IP "\fB\fB%d\fR\fR"
-# When the input key is an address of the form user@domain,
-# \fB%d\fR is replaced by the (RFC 2254) quoted domain part of the
-# address. Otherwise, \fB%d\fR is replaced by the entire
-# search string.
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the (RFC 2254) quoted domain part of the address.
+# Otherwise, the search is suppressed and returns no results.
+# .IP "\fB\fB%[SUD]\fR\fR"
+# The upper-case equivalents of the above expansions behave in the
+# \fBquery_filter\fR parameter identically to their lower-case
+# counter-parts. With the \fBresult_format\fR parameter (previously
+# called \fBresult_filter\fR see the COMPATIBILITY section and below),
+# they expand to the corresponding components of input key rather
+# than the result value.
+# .IP
+# The above %S, %U and %D expansions are available with Postfix 2.2
+# and later.
+# .IP "\fB\fB%[1-9]\fR\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. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the saerch is suppressed and returns
+# no results.
+# .IP
+# The above %1, ..., %9 expansions are available with Postfix 2.2
+# and later.
# .RE
# .IP
# The "domain" parameter described below limits the input
# addresses or addresses in non-matching domains are suppressed
# and return no results.
#
-# NOTE: DO NOT put quotes around the query filter.
-# .IP "\fBresult_filter (default: \fB%s\fR)\fR"
-# Format template applied to result attributes. Supports the
-# same expansions as the query_filter, and can be easily used
-# to append (or prepend) text. This parameter supports the
-# following '%' expansions:
+# NOTE: DO NOT put quotes around the \fBquery_filter\fR parameter.
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+# Called \fBresult_filter\fR in Postfix releases prior to 2.2.
+# Format template applied to result attributes. Most commonly used
+# to append (or prepend) text to the result. This parameter supports
+# the following '%' expansions:
# .RS
+# .IP "\fB\fB%%\fR\fR"
+# This is replaced by a literal '%' character. (Postfix 2.2 and later).
# .IP "\fB\fB%s\fR\fR"
-# This is replaced by the value of the result attribute.
+# 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. Otherwise, \fB%u\fR is replaced by the entire
-# attribute value.
+# address. When the result has an empty localpart it is skipped.
# .IP "\fB\fB%d\fR\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. Otherwise, \fB%d\fR is replaced by
-# the entire attribute value.
+# the attribute value. When the result is unqualified it
+# is skipped.
+# .IP "\fB\fB%[SUD1-9]\fR\fB"
+# The upper-case and decimal digit expansions interpolate
+# the parts of the input key rather than the result. Their
+# behaviour 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.
+# .IP
+# The above %S, %U, %D and %1, ..., %9 expansions are available with
+# Postfix 2.2 and later.
# .RE
# .IP
-# For example, using "result_filter = smtp:[%s]" allows one
+# 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 filter, multiple values
+# table. After applying the result format, multiple values
# are concatenated as comma separated strings. The expansion_limit
# and size_limit parameters explained below allow one to
# restrict the number of values in the result, which is
# The default value \fB%s\fR specifies that each
# attribute value should be used as is.
#
-# NOTE: DO NOT put quotes around the result filter!
+# This parameter was called \fBresult_filter\fR in Postfix
+# releases prior to 2.2. If no "result_format" is specified,
+# the value of "result_filter" will be used instead before
+# resorting to the default value. This provides compatibility
+# with old configuration files.
+#
+# NOTE: DO NOT put quotes around the result format!
# .IP "\fBdomain (default: no domain list)\fR"
# This is a list of domain names, paths to files, or
# dictionaries. When specified, only fully qualified search
# entries returned by the lookup, to be resolved to an email
# address.
# .ti +4
-# result_attribute = mailbox,maildrop
+# result_attribute = mailbox, maildrop
# .IP "\fBspecial_result_attribute (No default)\fR"
# The attribute(s) of directory entries that can contain DNs
# or URLs. If found, a recursive subsequent search is done
# values.
# .IP "\fBsize_limit (default: $expansion_limit)\fR"
# A limit on the number of LDAP entries returned by any single
-# LDAP query performed as part of the lookup. A setting of
+# LDAP search performed as part of the lookup. A setting of
# 0 disables the limit. Expansion of DN and URL references
# involves nested LDAP queries, each of which is separately
# subjected to this limit.
#
# Note: even a single LDAP entry can generate multiple lookup
# results, via multiple result attributes and/or multi-valued
-# result attributes. This limit caps the per query resource
+# result attributes. This limit caps the per search resource
# utilization on the LDAP server, not the final multiplicity
# of the lookup result. It is analogous to the "-z" option
# of "ldapsearch".
# The file /etc/postfix/mysql-aliases.cf has the same format as
# the Postfix main.cf file, and can specify the parameters
# described below.
-# ALTERNATIVE CONFIGURATION
+# BACKWARDS COMPATIBILITY
# .ad
# .fi
# For compatibility with other Postfix lookup tables, MySQL
# Note: with this form, the passwords for the MySQL sources are
# written in main.cf, which is normally world-readable. Support
# for this form will be removed in a future Postfix version.
+#
+# Postfix 2.2 has enhanced query interfaces for MySQL and PostreSQL,
+# these include features previously available only in the Postfix
+# LDAP client. In the new interface the SQL query is specified via
+# a single \fBquery\fR parameter (described in more detail below).
+# When the new \fBquery\fR parameter is not specified in the map
+# definition, Postfix reverts to the old interface, with the SQL
+# query constructed from the \fBselect_field\fR, \fBtable\fR,
+# \fBwhere_field\fR and \fBadditional_conditions\fR parameters.
+# The old interface will be gradually phased out. To migrate to
+# the new interface set:
+#
+# .ti +4
+# \fBquery\fR = SELECT [\fIselect_field\fR]
+# .ti +8
+# FROM [\fItable\fR]
+# .ti +8
+# WHERE [\fIwhere_field\fR] = '%s'
+# .ti +12
+# [\fIadditional_conditions\fR]
+#
+# Insert the value, not the name, of each legacy parameter. Note
+# that the \fBadditional_conditions\fR parameter is optional
+# and if not empty, will always start with \fBAND\fR.
# LIST MEMBERSHIP
# .ad
# .fi
# The database name on the servers. Example:
# .ti +4
# dbname = customer_database
+# .IP "\fBquery\fR"
+# The SQL query template used to search the database, where \fB%s\fR
+# is a substitute for the address Postfix is trying to resolve,
+# e.g.
+# .ti +4
+# query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+#
+# This parameter supports the following '%' expansions:
+# .RS
+# .IP "\fB\fB%%\fR\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB\fB%s\fR\fR"
+# This is replaced by the input key.
+# SQL quoting is used to make sure that the input key does not
+# add unexpected metacharacters.
+# .IP "\fB\fB%u\fR\fR"
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the SQL quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the query is suppressed and returns
+# no results.
+# .IP "\fB\fB%d\fR\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the SQL quoted domain part of the address.
+# Otherwise, the query is suppressed and returns no results.
+# .IP "\fB\fB%[SUD]\fR\fR"
+# The upper-case equivalents of the above expansions behave in the
+# \fBquery\fR parameter identically to their lower-case counter-parts.
+# With the \fBresult_format\fR parameter (see below), they expand the
+# input key rather than the result value.
+# .IP "\fB\fB%[1-9]\fR\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. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the query is suppressed and returns
+# no results.
+# .RE
+# .IP
+# The \fBdomain\fR parameter described below limits the input
+# keys to addresses in matching domains. When the \fBdomain\fR
+# parameter is non-empty, SQL queries for unqualified addresses
+# or addresses in non-matching domains are suppressed
+# and return no results.
+#
+# This parameter is available with Postfix 2.2. In prior releases
+# the SQL query was built from the separate parameters:
+# \fBselect_field\fR, \fBtable\fR, \fBwhere_field\fR and
+# \fBadditional_conditions\fR. The mapping from the old parameters
+# to the equivalent query is:
+#
+# .ti +4
+# SELECT [\fBselect_field\fR]
+# .ti +4
+# FROM [\fBtable\fR]
+# .ti +4
+# WHERE [\fBwhere_field\fR] = '%s'
+# .ti +10
+# [\fBadditional_conditions\fR]
+#
+# The '%s' in the \fBWHERE\fR clause expands to the escaped search string.
+# With Postfix 2.2 these legacy parameters are used if the \fBquery\fR
+# parameter is not specified.
+#
+# NOTE: DO NOT put quotes around the query parameter.
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+# Format template applied to result attributes. Most commonly used
+# to append (or prepend) text to the result. This parameter supports
+# the following '%' expansions:
+# .RS
+# .IP "\fB\fB%%\fR\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB\fB%s\fR\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\fB%d\fR\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\fB%[SUD1-9]\fR\fB"
+# The upper-case and decimal digit expansions interpolate
+# the parts of the input key rather than the result. Their
+# behaviour is identical to that described with \fBquery\fR,
+# and in fact because the input key is known in advance, queries
+# 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
+# and parameter explained below allows one to restrict the number
+# of values in the result, which is especially useful for maps that
+# must return at most one value.
+#
+# The default value \fB%s\fR specifies that each result value should
+# be used as is.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT put quotes around the result format!
+# .IP "\fBdomain (default: no domain list)\fR"
+# This is a list of domain names, paths to files, or
+# dictionaries. 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 MySQL server.
+# .ti +4
+# domain = postfix.org, hash:/etc/postfix/searchdomains
+#
+# It is best not to use SQL to store the domains eligible
+# for SQL lookups.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT define this parameter for local(8) aliases,
+# because the input keys are always unqualified.
+# .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.
# .PP
-# The following parameters are used to fill in a SELECT
-# query template of the form:
+# The following parameters can be used to fill in a
+# SELECT template statement of the form:
+#
# .ti +4
-# select [\fBselect_field\fR] from [\fBtable\fR] where
-# .ti +8
-# [\fBwhere_field\fR] = '$lookup' [\fBadditional_conditions\fR]
+# SELECT [\fBselect_field\fR]
+# .ti +4
+# FROM [\fBtable\fR]
+# .ti +4
+# WHERE [\fBwhere_field\fR] = '%s'
+# .ti +10
+# [\fBadditional_conditions\fR]
+#
+# The specifier %s is replaced by the search string, and is
+# escaped so if it contains single quotes or other odd characters,
+# it will not cause a parse error, or worse, a security problem.
#
-# $lookup contains the search string, and is escaped so if
-# it contains single quotes or other odd characters, it will
-# not cause a parse error, or worse, a security problem.
+# As of Postfix 2.2 this interface is obsolete, it is replaced
+# by the more general \fBquery\fR interface described above.
+# If the \fBquery\fR parameter is defined, the legacy parameters
+# are ignored. Please migrate to the new interface as the legacy
+# interface may be removed in a future release.
# .IP "\fBselect_field\fR"
# The SQL "select" parameter. Example:
# .ti +4
-# select_field = forw_addr
+# \fBselect_field\fR = forw_addr
# .IP "\fBtable\fR"
# The SQL "select .. from" table name. Example:
# .ti +4
-# table = mxaliases
+# \fBtable\fR = mxaliases
# .IP "\fBwhere_field\fR
# The SQL "select .. where" parameter. Example:
# .ti +4
-# where_field = alias
+# \fBwhere_field\fR = alias
# .IP "\fBadditional_conditions\fR
# Additional conditions to the SQL query. Example:
# .ti +4
-# additional_conditions = and status = 'paid'
+# \fBadditional_conditions\fR = AND status = 'paid'
# SEE ALSO
# postmap(1), Postfix lookup table maintenance
# postconf(5), configuration parameters
# The file /etc/postfix/pgsql-aliases.cf has the same format as
# the Postfix main.cf file, and can specify the parameters
# described below.
-# ALTERNATIVE CONFIGURATION
+# BACKWARDS COMPATIBILITY
# .ad
# .fi
# For compatibility with other Postfix lookup tables, PostgreSQL
# are written in main.cf, which is normally world-readable.
# Support for this form will be removed in a future Postfix
# version.
+#
+# Postfix 2.2 has enhanced query interfaces for MySQL and PostgreSQL,
+# these include features previously available only in the Postfix
+# LDAP client. In the new interface the SQL query is specified via
+# a single \fBquery\fR parameter (described in more detail below).
+# In Postfix 2.1 the parameter precedence was, from highest to lowest,
+# \fBselect_function\fR, \fBquery\fR and finally \fBselect_field\fR, ...
+#
+# With Postfix 2.2 the \fBquery\fR parameter has highest precedence,
+# and is used in preference to the still supported, but slated to be
+# phased out, \fBselect_function\fR, \fBselect_field\fR, \fBtable\fR,
+# \fBwhere_field\fR and \fBadditional_conditions\fR parameters. To
+# migrate to the new interface set:
+#
+# .ti +4
+# \fBquery\fR = SELECT \fIselect_function\fR('%s')
+#
+# or in the absense of \fBselection_function\fR, the lower precedence:
+#
+# .ti +4
+# \fBquery\fR = SELECT \fIselect_field\fR
+# .ti +8
+# FROM \fItable\fR
+# .ti +8
+# WHERE \fIwhere_field\fR = '%s'
+# .ti +12
+# \fIadditional_conditions\fR
+#
+# Use the value, not the name, of each legacy parameter. Note
+# that the \fBadditional_conditions\fR parameter is optional
+# and if not empty, will always start with \fBAND\fR.
# LIST MEMBERSHIP
# .ad
# .fi
# The database name on the servers. Example:
# .ti +4
# dbname = customer_database
-# .PP
-# The following parameters can be used to fill in a SELECT
-# template statement of the form:
-# .ti +4
-# select [\fBselect_field\fR] from [\fBtable\fR] where
-# .ti +8
-# [\fBwhere_field\fR] = '$lookup' [\fBadditional_conditions\fR]
-#
-# $lookup contains the search string, and is escaped so if
-# it contains single quotes or other odd characters, it will
-# not cause a parse error, or worse, a security problem.
-# .IP "\fBselect_field\fR"
-# The SQL "select" parameter. Example:
-# .ti +4
-# select_field = forw_addr
-# .IP "\fBtable\fR"
-# The SQL "select .. from" table name. Example:
-# .ti +4
-# table = mxaliases
-# .IP "\fBwhere_field\fR
-# The SQL "select .. where" parameter. Example:
-# .ti +4
-# where_field = alias
-# .IP "\fBadditional_conditions\fR
-# Additional conditions to the SQL query. Example:
-# .ti +4
-# additional_conditions = and status = 'paid'
-# .PP
-# The following parameters provide ways to override the default
-# SELECT statement. Setting them will instruct Postfix to ignore
-# the above \fBtable\fR, \fBselect_field\fR, \fBwhere_field\fR and
-# \fBadditional_conditions\fR parameters:
# .IP "\fBquery\fR"
-# This parameter specifies a complete SQL query. Example:
+# The SQL query template used to search the database, where \fB%s\fR
+# is a substitute for the address Postfix is trying to resolve,
+# e.g.
# .ti +4
-# query = select forw_addr from mxaliases where
-# .ti +8
-# alias = '%s' and status = 'paid'
+# query = SELECT replacement FROM aliases WHERE mailbox = '%s'
#
# This parameter supports the following '%' expansions:
# .RS
+# .IP "\fB\fB%%\fR\fR"
+# This is replaced by a literal '%' character. (Postfix 2.2 and later)
# .IP "\fB\fB%s\fR\fR"
-# This is replaced by the input key. Quoting is used to make sure
-# that the input key does not add unexpected metacharacters.
+# This is replaced by the input key.
+# SQL quoting is used to make sure that the input key does not
+# add unexpected metacharacters.
# .IP "\fB\fB%u\fR\fR"
-# When the input key is an address of the form user@domain,
-# \fB%u\fR is replaced by the quoted local part of the address.
-# If no domain is specified, \fB%u\fR is replaced by the entire
-# search string.
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the SQL quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the query is suppressed and returns
+# no results.
+# .IP "\fB\fB%d\fR\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the SQL quoted domain part of the address.
+# Otherwise, the query is suppressed and returns no results.
+# .IP "\fB\fB%[SUD]\fR\fR"
+# The upper-case equivalents of the above expansions behave in the
+# \fBquery\fR parameter identically to their lower-case counter-parts.
+# With the \fBresult_format\fR parameter (see below), they expand the
+# input key rather than the result value.
+# .IP
+# The above %S, %U and %D expansions are available with Postfix 2.2
+# and later
+# .IP "\fB\fB%[1-9]\fR\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. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the query is suppressed and returns
+# no results.
+# .IP
+# The above %1, ... %9 expansions are available with Postfix 2.2
+# and later
+# .RE
+# .IP
+# The \fBdomain\fR parameter described below limits the input
+# keys to addresses in matching domains. When the \fBdomain\fR
+# parameter is non-empty, SQL queries for unqualified addresses
+# or addresses in non-matching domains are suppressed
+# and return no results.
+#
+# The precedence of this parameter has changed with Postfix 2.2,
+# in prior releases the precedence was, from highest to lowest,
+# \fBselect_function\fR, \fBquery\fR, \fBselect_field\fR, ...
+#
+# With Postfix 2.2 the \fBquery\fR parameter has highest precedence,
+# see COMPATIBILITY above.
+#
+# NOTE: DO NOT put quotes around the \fBquery\fR parameter.
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+# Format template applied to result attributes. Most commonly used
+# to append (or prepend) text to the result. This parameter supports
+# the following '%' expansions:
+# .RS
+# .IP "\fB\fB%%\fR\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB\fB%s\fR\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\fB%d\fR\fR"
-# When the input key is an address of the form user@domain,
-# \fB%d\fR is replaced by the quoted domain part of the address.
-# When the input key has no domain qualifier, \fB%d\fR is replaced
-# by the entire search string.
+# 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\fB%[SUD1-9]\fR\fB"
+# The upper-case and decimal digit expansions interpolate
+# the parts of the input key rather than the result. Their
+# behaviour is identical to that described with \fBquery\fR,
+# and in fact because the input key is known in advance, queries
+# 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
+# and parameter explained below allows one to restrict the number
+# of values in the result, which is especially useful for maps that
+# must return at most one value.
+#
+# The default value \fB%s\fR specifies that each result value should
+# be used as is.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT put quotes around the result format!
+# .IP "\fBdomain (default: no domain list)\fR"
+# This is a list of domain names, paths to files, or
+# dictionaries. 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 PostgreSQL server.
+# .ti +4
+# domain = postfix.org, hash:/etc/postfix/searchdomains
+#
+# It is best not to use SQL to store the domains eligible
+# for SQL lookups.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT define this parameter for local(8) aliases,
+# because the input keys are always unqualified.
+# .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.
+# .PP
+# Pre-Postfix 2.2 legacy interfaces:
# .IP "\fBselect_function\fR"
# This parameter specifies a database function name. Example:
# .ti +4
#
# This is equivalent to:
# .ti +4
-# query = select my_lookup_user_alias('%s')
+# query = SELECT my_lookup_user_alias('%s')
+#
+# This parameter overrides the legacy table-related fields (described
+# below). With Postfix versions prior to 2.2, it also overrides the
+# \fBquery\fR parameter. Starting with Postfix 2.2, the \fBquery\fR
+# parameter has highest precedence, and this parameter is deprecated.
+# Please migrate to the new \fBquery\fR interface as this interface
+# is slated to be phased out.
+# .PP
+# The following parameters (with lower precedence than the
+# \fBselect_function\fR interface described above) can be used to
+# build the SQL select statement as follows:
#
-# and overrides both the \fBquery\fR parameter and the table-related
-# fields above.
+# .ti +4
+# SELECT [\fBselect_field\fR]
+# .ti +4
+# FROM [\fBtable\fR]
+# .ti +4
+# WHERE [\fBwhere_field\fR] = '%s'
+# .ti +10
+# [\fBadditional_conditions\fR]
#
-# As of June 2002, if the function returns a single row and
-# a single column AND that value is NULL, then the result
-# will be treated as if the key was not in the dictionary.
+# The specifier %s is replaced with each lookup by the lookup key
+# and is escaped so if it contains single quotes or other odd
+# characters, it will not cause a parse error, or worse, a security
+# problem.
#
-# Future versions will allow functions to return result sets.
+# Starting with Postfix 2.2, this interface is obsoleted by the more
+# general \fBquery\fR interface described above. If higher precedence
+# the \fBquery\fR or \fBselect_function\fR parameters described above
+# are defined, these parameters are ignored. Please migrate to the new
+# \fBquery\fR interface as this interface is slated to be phased out.
+# .IP "\fBselect_field\fR"
+# The SQL "select" parameter. Example:
+# .ti +4
+# \fBselect_field\fR = forw_addr
+# .IP "\fBtable\fR"
+# The SQL "select .. from" table name. Example:
+# .ti +4
+# \fBtable\fR = mxaliases
+# .IP "\fBwhere_field\fR
+# The SQL "select .. where" parameter. Example:
+# .ti +4
+# \fBwhere_field\fR = alias
+# .IP "\fBadditional_conditions\fR
+# Additional conditions to the SQL query. Example:
+# .ti +4
+# \fBadditional_conditions\fR = AND status = 'paid'
# SEE ALSO
# postmap(1), Postfix lookup table manager
# postconf(5), configuration parameters
This is enabled by default.
</p>
+<p> Note: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
<p>
Example:
</p>
to eliminate the delay.
</p>
+<p> Note: with Postfix version 2.2, message header address mapping
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
<p>
Examples:
</p>
<p> The Internet protocols Postfix will attempt to use when making
or accepting connections. Specify one or more of "ipv4"
or "ipv6", separated by whitespace or commas. The form
-"all" is equivalent to "ipv4, ipv6". </p>
+"all" is equivalent to "ipv4, ipv6" or "ipv4", depending
+on whether the operating system implements IPv6. </p>
<p> This feature is available in Postfix version 2.2 and later. </p>
but strips "user@any.thing.else.example.com" to "user@example.com".
</p>
+<p> Note: with Postfix version 2.2, message header address masquerading
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+
<p>
Example:
</p>
enabled by default.
</p>
+<p> Note: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
<p>
Example:
</p>
</p>
<p>
-This feature is enabled by default and must not be turned off.
+Note 1: This feature is enabled by default and must not be turned off.
Postfix does not support domain-less addresses.
</p>
+<p> Note 2: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
%PARAM append_dot_mydomain yes
<p>
</p>
<p>
-This feature is enabled by default. If disabled, users will not be
+Note 1: This feature is enabled by default. If disabled, users will not be
able to send mail to "user@partialdomainname" but will have to
specify full domain names instead.
</p>
+<p> Note 2: With Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
%PARAM application_event_drain_time 100s
<p>
SHELL = /bin/sh
SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \
canon_addr.c cfg_parser.c cleanup_strerror.c cleanup_strflags.c \
- clnt_stream.c debug_peer.c debug_process.c defer.c \
+ clnt_stream.c debug_peer.c debug_process.c defer.c db_common.c \
deliver_completed.c deliver_flock.c deliver_pass.c deliver_request.c \
dict_ldap.c dict_mysql.c dict_pgsql.c dict_proxy.c domain_list.c \
dot_lockfile.c dot_lockfile_as.c ext_prop.c file_id.c flush_clnt.c \
wildcard_inet_addr.c valid_mailhost_addr.c
OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \
- clnt_stream.o debug_peer.o debug_process.o defer.o \
+ clnt_stream.o debug_peer.o debug_process.o defer.o db_common.o \
deliver_completed.o deliver_flock.o deliver_pass.o deliver_request.o \
dict_ldap.o dict_mysql.o dict_pgsql.o dict_proxy.o domain_list.o \
dot_lockfile.o dot_lockfile_as.o ext_prop.o file_id.o flush_clnt.o \
resolve_local.h rewrite_clnt.h sent.h smtp_stream.h split_addr.h \
string_list.h strip_addr.h sys_exits.h timed_ipc.h tok822.h \
trace.h verify.h verify_clnt.h verp_sender.h virtual8_maps.h \
- xtext.h scache.h user_acl.h ehlo_mask.h \
+ xtext.h scache.h user_acl.h ehlo_mask.h db_common.h \
wildcard_inet_addr.h valid_mailhost_addr.h
TESTSRC = rec2stream.c stream2rec.c recdump.c
DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
clnt_stream.o: ../../include/attr.h
clnt_stream.o: mail_params.h
clnt_stream.o: clnt_stream.h
+db_common.o: db_common.c
+db_common.o: ../../include/sys_defs.h
+db_common.o: cfg_parser.h
+db_common.o: ../../include/mymalloc.h
+db_common.o: ../../include/vstring.h
+db_common.o: ../../include/vbuf.h
+db_common.o: ../../include/msg.h
+db_common.o: ../../include/dict.h
+db_common.o: ../../include/vstream.h
+db_common.o: ../../include/argv.h
+db_common.o: db_common.h
+db_common.o: string_list.h
+db_common.o: ../../include/match_list.h
+db_common.o: ../../include/match_ops.h
debug_peer.o: debug_peer.c
debug_peer.o: ../../include/sys_defs.h
debug_peer.o: ../../include/msg.h
deliver_request.o: deliver_request.h
dict_ldap.o: dict_ldap.c
dict_ldap.o: ../../include/sys_defs.h
+dict_ldap.o: ../../include/msg.h
+dict_ldap.o: ../../include/mymalloc.h
+dict_ldap.o: ../../include/vstring.h
+dict_ldap.o: ../../include/vbuf.h
+dict_ldap.o: ../../include/dict.h
+dict_ldap.o: ../../include/vstream.h
+dict_ldap.o: ../../include/argv.h
+dict_ldap.o: ../../include/stringops.h
+dict_ldap.o: ../../include/binhash.h
+dict_ldap.o: cfg_parser.h
+dict_ldap.o: db_common.h
+dict_ldap.o: string_list.h
+dict_ldap.o: ../../include/match_list.h
+dict_ldap.o: ../../include/match_ops.h
+dict_ldap.o: dict_ldap.h
dict_mysql.o: dict_mysql.c
dict_mysql.o: ../../include/sys_defs.h
+dict_mysql.o: ../../include/dict.h
+dict_mysql.o: ../../include/vstream.h
+dict_mysql.o: ../../include/vbuf.h
+dict_mysql.o: ../../include/argv.h
+dict_mysql.o: ../../include/msg.h
+dict_mysql.o: ../../include/mymalloc.h
+dict_mysql.o: ../../include/vstring.h
+dict_mysql.o: ../../include/split_at.h
+dict_mysql.o: ../../include/find_inet.h
+dict_mysql.o: ../../include/myrand.h
+dict_mysql.o: ../../include/events.h
+dict_mysql.o: cfg_parser.h
+dict_mysql.o: db_common.h
+dict_mysql.o: string_list.h
+dict_mysql.o: ../../include/match_list.h
+dict_mysql.o: ../../include/match_ops.h
+dict_mysql.o: dict_mysql.h
dict_pgsql.o: dict_pgsql.c
dict_pgsql.o: ../../include/sys_defs.h
+dict_pgsql.o: ../../include/dict.h
+dict_pgsql.o: ../../include/vstream.h
+dict_pgsql.o: ../../include/vbuf.h
+dict_pgsql.o: ../../include/argv.h
+dict_pgsql.o: ../../include/msg.h
+dict_pgsql.o: ../../include/mymalloc.h
+dict_pgsql.o: ../../include/vstring.h
+dict_pgsql.o: ../../include/split_at.h
+dict_pgsql.o: ../../include/find_inet.h
+dict_pgsql.o: ../../include/myrand.h
+dict_pgsql.o: ../../include/events.h
+dict_pgsql.o: cfg_parser.h
+dict_pgsql.o: db_common.h
+dict_pgsql.o: string_list.h
+dict_pgsql.o: ../../include/match_list.h
+dict_pgsql.o: ../../include/match_ops.h
+dict_pgsql.o: dict_pgsql.h
dict_proxy.o: dict_proxy.c
dict_proxy.o: ../../include/sys_defs.h
dict_proxy.o: ../../include/msg.h
--- /dev/null
+/*++
+/* NAME
+/* db_common 3
+/* SUMMARY
+/* utilities common to network based dictionaries
+/* SYNOPSIS
+/* #include "db_common.h"
+/*
+/* int db_common_parse(dict, ctx, format, query)
+/* DICT *dict;
+/* void **ctx;
+/* const char *format;
+/* int query;
+/*
+/* void db_common_free_context(ctx)
+/* void *ctx;
+/*
+/* int db_common_expand(ctx, format, value, key, buf, quote_func);
+/* void *ctx;
+/* const char *format;
+/* const char *value;
+/* const char *key;
+/* VSTRING *buf;
+/* void (*quote_func)(DICT *, const char *, VSTRING *);
+/*
+/* int db_common_check_domain(domain_list, addr);
+/* STRING_LIST *domain_list;
+/* const char *addr;
+/*
+/* void db_common_sql_build_query(query,parser);
+/* VSTRING *query;
+/* CFG_PARSER *parser;
+/*
+/* DESCRIPTION
+/* This module implements utilities common to network based dictionaries.
+/*
+/* \fIdb_common_parse\fR parses query and result substitution templates.
+/* It must be called for each template before any calls to
+/* \fIdb_common_expand\fR. The \fIctx\fB argument must be initialized to
+/* a reference to a (void *)0 before the first template is parsed, this
+/* causes memory for the context to be allocated and the new pointer is
+/* stored in *ctx. When the dictionary is closed, this memory must be
+/* freed with a final call to \fBdb_common_free_context\fR.
+/*
+/* Calls for additional templates associated with the same map must use the
+/* same ctx argument. The context accumulates run-time lookup key and result
+/* validation information (inapplicable keys or results are skipped) and is
+/* needed later in each call of \fIdb_common_expand\fR. A non-zero return
+/* value indicates that data-depedent '%' expansions were found in the input
+/* template.
+/*
+/* \fIdb_common_expand\fR expands the specifiers in \fIformat\fR.
+/* When the input data lacks all fields needed for the expansion, zero
+/* is returned and the query or result should be skipped. Otherwise
+/* the expansion is appended to the result buffer (after a comma if the
+/* the result buffer is not empty).
+/*
+/* If not NULL, the \fBquote_func\fR callback performs database-specific
+/* quoting of each variable before expansion.
+/* \fBvalue\fR is the lookup key for query expansion and result for result
+/* expansion. \fBkey\fR is NULL for query expansion and the lookup key for
+/* result expansion.
+/* .PP
+/* The following '%' expansions are performed on \fBvalue\fR:
+/* .IP %%
+/* A literal percent character.
+/* .IP %s
+/* The entire lookup key \fIaddr\fR.
+/* .IP %u
+/* If \fBaddr\fR is a fully qualified address, the local part of the
+/* address. Otherwise \fIaddr\fR.
+/* .IP %d
+/* If \fIaddr\fR is a fully qualified address, the domain part of the
+/* address. Otherwise the query against the database is suppressed and
+/* the lookup returns no results.
+/*
+/* The following '%' expansions are performed on the lookup \fBkey\fR:
+/* .IP %S
+/* The entire lookup key \fIkey\fR.
+/* .IP %U
+/* If \fBkey\fR is a fully qualified address, the local part of the
+/* address. Otherwise \fIkey\fR.
+/* .IP %D
+/* If \fIkey\fR is a fully qualified address, the domain part of the
+/* address. Otherwise the query against the database is suppressed and
+/* the lookup returns no results.
+/*
+/* .PP
+/* \fIdb_common_check_domain\fR checks domain list so that query optimization
+/* can be performed
+/*
+/* .PP
+/* \fIdb_common_sql_build_query\fR builds the "default"(backwards compatible)
+/* query from the 'table', 'select_field', 'where_field' and
+/* 'additional_conditions' parameters, checking for errors.
+/*
+/* DIAGNOSTICS
+/* Fatal errors: invalid substitution format, invalid string_list pattern,
+/* insufficient parameters.
+/* SEE ALSO
+/* dict(3) dictionary manager
+/* string_list(3) string list pattern matching
+/* match_ops(3) simple string or host pattern matching
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*
+/* Jose Luis Tallon
+/* G4 J.E. - F.I. - U.P.M.
+/* Campus de Montegancedo, S/N
+/* E-28660 Madrid, SPAIN
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+ /*
+ * System library.
+ */
+#include "sys_defs.h"
+#include <stddef.h>
+#include <string.h>
+
+ /*
+ * Global library.
+ */
+#include "cfg_parser.h"
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <vstring.h>
+#include <msg.h>
+#include <dict.h>
+
+ /*
+ * Application specific
+ */
+#include "db_common.h"
+
+#define DB_COMMON_KEY_DOMAIN (1 << 0) /* Need lookup key domain */
+#define DB_COMMON_KEY_USER (1 << 1) /* Need lookup key localpart */
+#define DB_COMMON_VALUE_DOMAIN (1 << 2) /* Need result domain */
+#define DB_COMMON_VALUE_USER (1 << 3) /* Need result localpart */
+
+typedef struct {
+ DICT *dict;
+ int flags;
+ int nparts;
+} DB_COMMON_CTX;
+
+/* db_common_parse - validate query or result template */
+
+int db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *)*ctxPtr;
+ const char *cp;
+ int dynamic = 0;
+
+ if (ctx == 0) {
+ ctx = (DB_COMMON_CTX *)(*ctxPtr = mymalloc(sizeof *ctx));
+ ctx->dict = dict;
+ ctx->flags = 0;
+ ctx->nparts = 0;
+ }
+
+ for (cp = format; *cp; ++cp)
+ if (*cp == '%')
+ switch (*++cp) {
+ case '%':
+ break;
+ case 'u':
+ ctx->flags |=
+ query ? DB_COMMON_KEY_USER : DB_COMMON_VALUE_USER;
+ dynamic = 1;
+ break;
+ case 'd':
+ ctx->flags |=
+ query ? DB_COMMON_KEY_DOMAIN : DB_COMMON_VALUE_DOMAIN;
+ dynamic = 1;
+ break;
+ case 's': case 'S':
+ dynamic = 1;
+ break;
+ case 'U':
+ ctx->flags |= DB_COMMON_KEY_USER;
+ dynamic = 1;
+ break;
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ if (ctx->nparts < *cp - '0')
+ ctx->nparts = *cp - '0';
+ /* FALLTHROUGH */
+ case 'D':
+ ctx->flags |= DB_COMMON_KEY_DOMAIN;
+ dynamic = 1;
+ break;
+ default:
+ msg_fatal("db_common_parse: %s: Invalid %s template: %s",
+ dict->name, query ? "query" : "result", format);
+ }
+ return dynamic;
+}
+
+/* db_common_free_ctx - free parse context */
+
+void db_common_free_ctx(void *ctxPtr)
+{
+ myfree((char *)ctxPtr);
+}
+
+/* db_common_expand - expand query and result templates */
+
+int db_common_expand(void *ctxArg, const char *format, const char *value,
+ const char *key, VSTRING *result,
+ db_quote_callback_t quote_func)
+{
+ char *myname = "db_common_expand";
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *)ctxArg;
+ const char *vdomain = 0;
+ const char *kdomain = 0;
+ char *vuser = 0;
+ char *kuser = 0;
+ ARGV *parts = 0;
+ int i;
+ const char *cp;
+
+ /* Skip NULL or empty values */
+ if (value == 0 || *value == 0)
+ return (0);
+
+ if (key) {
+ /* This is a result template and the input value is the result */
+ if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER))
+ if ((vdomain = strrchr(value, '@')) != 0)
+ ++vdomain;
+
+ if ((!vdomain || !*vdomain) && (ctx->flags&DB_COMMON_VALUE_DOMAIN) != 0
+ || vdomain == value + 1 && (ctx->flags&DB_COMMON_VALUE_USER) != 0)
+ return (0);
+
+ /* The result format may use the local or domain part of the key */
+ if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
+ if ((kdomain = strrchr(key, '@')) != 0)
+ ++kdomain;
+
+ /*
+ * The key should already be checked before the query. No harm if
+ * the query did not get optimized out, so we just issue a warning.
+ */
+ if ((!kdomain || !*kdomain) && (ctx->flags&DB_COMMON_KEY_DOMAIN) != 0
+ || kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0) {
+ msg_warn("%s: %s: lookup key '%s' skipped after query", myname,
+ ctx->dict->name, value);
+ return (0);
+ }
+ } else {
+ /* This is a query template and the input value is the lookup key */
+ if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
+ if ((vdomain = strrchr(value, '@')) != 0)
+ ++vdomain;
+
+ if ((!vdomain || !*vdomain) && (ctx->flags&DB_COMMON_KEY_DOMAIN) != 0
+ || vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)
+ return (0);
+ }
+
+ if (ctx->nparts > 0) {
+ parts = argv_split(key ? kdomain : vdomain, ".");
+ /*
+ * Skip domains that lack enough labels to fill-in the template.
+ */
+ if (parts->argc < ctx->nparts) {
+ argv_free(parts);
+ return (0);
+ }
+ /*
+ * Skip domains with leading, consecutive or trailing '.'
+ * separators among the required labels.
+ */
+ for (i = 0; i < ctx->nparts; i++)
+ if (*parts->argv[parts->argc-i-1] == 0) {
+ argv_free(parts);
+ return (0);
+ }
+ }
+
+ if (VSTRING_LEN(result) > 0)
+ VSTRING_ADDCH(result, ',');
+
+#define QUOTE_VAL(d, q, v, buf) \
+ (q ? q(d, v, buf) : vstring_strcat(buf, v))
+
+ /*
+ * Replace all instances of %s with the address to look up. Replace
+ * %u with the user portion, and %d with the domain portion. "%%"
+ * expands to "%". lowercase -> addr, uppercase -> key
+ */
+ for (cp = format; *cp; cp++) {
+ if (*cp == '%') {
+ switch (*++cp) {
+
+ case '%':
+ VSTRING_ADDCH(result, '%');
+ break;
+
+ case 's':
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'u':
+ if (vdomain) {
+ if (vuser == 0)
+ vuser = mystrndup(value, vdomain - value - 1);
+ QUOTE_VAL(ctx->dict, quote_func, vuser, result);
+ }
+ else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'd':
+ QUOTE_VAL(ctx->dict, quote_func, vdomain, result);
+ break;
+
+ case 'S':
+ if (key)
+ QUOTE_VAL(ctx->dict, quote_func, key, result);
+ else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'U':
+ if (key) {
+ if (kdomain) {
+ if (kuser == 0)
+ kuser = mystrndup(key, kdomain - key - 1);
+ QUOTE_VAL(ctx->dict, quote_func, kuser, result);
+ }
+ else
+ QUOTE_VAL(ctx->dict, quote_func, key, result);
+ } else {
+ if (vdomain) {
+ if (vuser == 0)
+ vuser = mystrndup(value, vdomain - value - 1);
+ QUOTE_VAL(ctx->dict, quote_func, vuser, result);
+ }
+ else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ }
+ break;
+
+ case 'D':
+ if (key)
+ QUOTE_VAL(ctx->dict, quote_func, kdomain, result);
+ else
+ QUOTE_VAL(ctx->dict, quote_func, vdomain, result);
+ break;
+
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ QUOTE_VAL(ctx->dict, quote_func,
+ parts->argv[parts->argc-(*cp-'0')], result);
+ break;
+
+ default:
+ msg_fatal("%s: %s: invalid %s template '%s'", myname,
+ ctx->dict->name, key ? "result" : "query",
+ format);
+ }
+ } else
+ VSTRING_ADDCH(result, *cp);
+ }
+ VSTRING_TERMINATE(result);
+
+ if (vuser)
+ myfree(vuser);
+ if (kuser)
+ myfree(kuser);
+ if (parts)
+ argv_free(parts);
+
+ return (1);
+}
+
+
+/* db_common_check_domain - check domain list */
+
+int db_common_check_domain(STRING_LIST *domain_list, const char *addr)
+{
+ char *domain;
+
+ if (domain_list) {
+ if ((domain = strrchr(addr, '@')) != NULL)
+ ++domain;
+ if (domain == NULL || domain == addr + 1)
+ return (0);
+ if (match_list_match(domain_list, domain) == 0)
+ return (0);
+ }
+ return (1);
+}
+
+/* db_common_sql_build_query -- build query for SQL maptypes */
+
+void db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser)
+{
+ char *myname = "db_common_sql_build_query";
+ char *table;
+ char *select_field;
+ char *where_field;
+ char *additional_conditions;
+
+ /*
+ * Build "old style" query: "select %s from %s where %s"
+ */
+ if ((table = cfg_get_str(parser, "table", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'table' parameter not defined", myname);
+
+ if ((select_field = cfg_get_str(parser, "select_field", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'select_field' parameter not defined", myname);
+
+ if ((where_field = cfg_get_str(parser, "where_field", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'where_field' parameter not defined", myname);
+
+ additional_conditions = cfg_get_str(parser, "additional_conditions",
+ "", 0, 0);
+
+ vstring_sprintf(query, "SELECT %s FROM %s WHERE %s='%%s' %s",
+ select_field, table, where_field,
+ additional_conditions);
+
+ myfree(table);
+ myfree(select_field);
+ myfree(where_field);
+ myfree(additional_conditions);
+}
--- /dev/null
+#ifndef _DB_COMMON_H_INCLUDED_
+#define _DB_COMMON_H_INCLUDED_
+
+/*++
+/* NAME
+/* db_common 3h
+/* SUMMARY
+/* utilities common to network based dictionaries
+/* SYNOPSIS
+/* #include "db_common.h"
+/* DESCRIPTION
+/* .nf
+ */
+
+ /*
+ * External interface.
+ */
+#include "dict.h"
+#include "string_list.h"
+
+typedef void (*db_quote_callback_t)(DICT *, const char *, VSTRING *);
+
+extern int db_common_parse(DICT *, void **, const char *, int);
+extern void db_common_free_ctx(void *);
+extern int db_common_expand(void *, const char *, const char *,
+ const char *, VSTRING *, db_quote_callback_t);
+extern int db_common_check_domain(STRING_LIST *, const char *);
+extern void db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*
+/* Jose Luis Tallon
+/* G4 J.E. - F.I. - U.P.M.
+/* Campus de Montegancedo, S/N
+/* E-28660 Madrid, SPAIN
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+#endif
+
/* #include <dict_ldap.h>
/*
/* DICT *dict_ldap_open(attribute, dummy, dict_flags)
-/* const char *attribute;
+/* const char *ldapsource;
/* int dummy;
/* int dict_flags;
/* DESCRIPTION
/* .IP timeout
/* Deadline for LDAP open() and LDAP search() .
/* .IP query_filter
-/* The filter used to search for directory entries, for example
-/* \fI(mailacceptinggeneralid=%s)\fR.
-/* .IP result_filter
-/* The filter used to expand results from queries. Default is
-/* \fI%s\fR.
+/* The search filter template used to search for directory entries,
+/* for example \fI(mailacceptinggeneralid=%s)\fR. See ldap_table(5)
+/* for details.
+/* .IP result_format
+/* The result template used to expand results from queries. Default
+/* is \fI%s\fR. See ldap_table(5) for details. Also supported under
+/* the name \fIresult_filter\fR for compatibility with older releases.
/* .IP result_attribute
/* The attribute(s) returned by the search, in which to find
/* RFC822 addresses, for example \fImaildrop\fR.
/* Utility library. */
-#include "match_list.h"
-#include "match_ops.h"
#include "msg.h"
#include "mymalloc.h"
#include "vstring.h"
/* Global library. */
#include "cfg_parser.h"
+#include "db_common.h"
/* Application-specific. */
*/
typedef struct {
DICT dict; /* generic member */
- CFG_PARSER *parser;
- char *ldapsource;
+ CFG_PARSER *parser; /* common parameter parser */
+ char *query; /* db_common_expand() query */
+ char *result_format; /* db_common_expand() result_format */
+ STRING_LIST *domain; /* restrict queries to these domains */
+ void *ctx; /* db_common_parse() context */
+ int dynamic_base; /* Search base has substitutions? */
+ int expansion_limit;
char *server_host;
int server_port;
int scope;
char *search_base;
- MATCH_LIST *domain;
- char *query_filter;
- char *result_filter;
ARGV *result_attributes;
int num_attributes; /* rest of list is DN's. */
int bind;
int timeout;
int dereference;
long recursion_limit;
- long expansion_limit;
long size_limit;
int chase_referrals;
int debuglevel;
#define DICT_LDAP_CONN(d) ((LDAP_CONN *)((d)->ht->value))
+
+
+/*
+ * Quoting rules.
+ */
+
+/* rfc2253_quote - Quote input key for safe inclusion in the search base */
+
+static void rfc2253_quote(DICT *unused, const char *name, VSTRING *result)
+{
+ unsigned char *sub = (unsigned char *)name;
+ size_t len;
+
+ /*
+ * The RFC only requires quoting of a leading or trailing space,
+ * but it is harmless to quote whitespace everywhere. Similarly,
+ * we quote all '#' characters, even though only the leading '#'
+ * character requires quoting per the RFC.
+ */
+ while (*sub)
+ if ((len = strcspn(sub, " \t\"#+,;<>\\")) > 0) {
+ vstring_strncat(result, sub, len);
+ sub += len;
+ } else
+ vstring_sprintf_append(result, "\\%02X", *(sub++));
+}
+
+/* rfc2254_quote - Quote input key for safe inclusion in the query filter */
+
+static void rfc2254_quote(DICT *unused, const char *name, VSTRING *result)
+{
+ unsigned char *sub = (unsigned char *)name;
+ size_t len;
+
+ /*
+ * If any characters in the supplied address should be escaped per RFC
+ * 2254, do so. Thanks to Keith Stevenson and Wietse. And thanks to
+ * Samuel Tardieu for spotting that wildcard searches were being done in
+ * the first place, which prompted the ill-conceived lookup_wildcards
+ * parameter and then this more comprehensive mechanism.
+ */
+ while (*sub)
+ if ((len = strcspn(sub, "*()\\")) > 0) {
+ vstring_strncat(result, sub, len);
+ sub += len;
+ } else
+ vstring_sprintf_append(result, "\\%02X", *(sub++));
+}
+
static BINHASH *conn_hash = 0;
#if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT)
#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN)
if (dict_ldap->debuglevel > 0 &&
ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN,
- (LDAP_CONST *) dict_ldap_logprint) != LBER_OPT_SUCCESS)
+ (LDAP_CONST void *) dict_ldap_logprint) != LBER_OPT_SUCCESS)
msg_warn("%s: Unable to set ber logprint function.", myname);
#if defined(LBER_OPT_DEBUG_LEVEL)
if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL,
if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT,
&dict_ldap->size_limit) != LDAP_OPT_SUCCESS)
msg_warn("%s: %s: Unable to set query result size limit to %ld.",
- myname, dict_ldap->ldapsource, dict_ldap->size_limit);
+ myname, dict_ldap->parser->name, dict_ldap->size_limit);
}
/*
if (msg_verbose)
msg_info("%s: Cached connection handle for LDAP source %s",
- myname, dict_ldap->ldapsource);
+ myname, dict_ldap->parser->name);
return (0);
}
vstring_free(keybuf);
}
-/*
- * expand a filter (lookup or result)
- */
-static void dict_ldap_expand_filter(char *ldapsource, char *filter,
- char *value, VSTRING *out)
-{
- char *myname = "dict_ldap_expand_filter";
- char *sub,
- *end;
-
- /*
- * Yes, replace all instances of %s with the address to look up. Replace
- * %u with the user portion, and %d with the domain portion.
- */
- sub = filter;
- end = sub + strlen(filter);
- while (sub < end) {
-
- /*
- * Make sure it's %[sud] and not something else. For backward
- * compatibilty, treat anything other than %u or %d as %s, with a
- * warning.
- */
- if (*(sub) == '%') {
- char *u = value;
- char *p = strrchr(u, '@');
-
- switch (*(sub + 1)) {
- case 'd':
- if (p)
- vstring_strcat(out, p + 1);
- break;
- case 'u':
- if (p)
- vstring_strncat(out, u, p - u);
- else
- vstring_strcat(out, u);
- break;
- default:
- msg_warn("%s: %s: Invalid filter substitution format '%%%c'!",
- myname, ldapsource, *(sub + 1));
- /* fall through */
- case 's':
- vstring_strcat(out, u);
- break;
- }
- sub++;
- } else
- vstring_strncat(out, sub, 1);
- sub++;
- }
-}
-
/*
* dict_ldap_get_values: for each entry returned by a search, get the values
* of all its attributes. Recurses to resolve any DN or URL values found.
* are thanks to LaMont Jones.
*/
static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res,
- VSTRING *result)
+ VSTRING *result, const char* name)
{
static int recursion = 0;
static int expansion;
* LDAP should not, but may produce more than the requested maximum
* number of entries.
*/
- if (dict_errno == 0 && ++entries > dict_ldap->size_limit
- && dict_ldap->size_limit) {
- msg_warn("%s[%d]: %s: Query size limit (%ld) exceeded", myname,
- recursion, dict_ldap->ldapsource, dict_ldap->size_limit);
+ if (dict_errno == 0
+ && dict_ldap->size_limit
+ && ++entries > dict_ldap->size_limit) {
+ msg_warn("%s[%d]: %s: Query size limit (%ld) exceeded",
+ myname, recursion, dict_ldap->parser->name,
+ dict_ldap->size_limit);
dict_errno = DICT_ERR_RETRY;
}
for (attr = ldap_first_attribute(dict_ldap->ld, entry, &ber);
if (i < dict_ldap->num_attributes) {
/* Ordinary result attribute */
for (i = 0; vals[i] != NULL; i++) {
- if (++expansion > dict_ldap->expansion_limit &&
- dict_ldap->expansion_limit) {
- msg_warn("%s[%d]: %s: Expansion limit exceeded at"
- " result attribute %s=%s", myname, recursion,
- dict_ldap->ldapsource, attr, vals[i]);
+ if (db_common_expand(dict_ldap->ctx,
+ dict_ldap->result_format, vals[i],
+ name, result, 0)
+ && dict_ldap->expansion_limit > 0
+ && ++expansion > dict_ldap->expansion_limit) {
+ msg_warn("%s[%d]: %s: Expansion limit exceeded for key: '%s'",
+ myname, recursion, dict_ldap->parser->name, name);
dict_errno = DICT_ERR_RETRY;
break;
}
- if (VSTRING_LEN(result) > 0)
- vstring_strcat(result, ",");
- if (dict_ldap->result_filter == NULL)
- vstring_strcat(result, vals[i]);
- else
- dict_ldap_expand_filter(dict_ldap->ldapsource,
- dict_ldap->result_filter,
- vals[i], result);
}
if (dict_errno != 0)
continue;
}
switch (rc) {
case LDAP_SUCCESS:
- dict_ldap_get_values(dict_ldap, resloop, result);
+ dict_ldap_get_values(dict_ldap, resloop, result, name);
break;
case LDAP_NO_SUCH_OBJECT:
} else if (recursion >= dict_ldap->recursion_limit
&& dict_ldap->result_attributes->argv[i]) {
msg_warn("%s[%d]: %s: Recursion limit exceeded"
- " for special attribute %s=%s",
- myname, recursion, dict_ldap->ldapsource, attr, vals[0]);
+ " for special attribute %s=%s", myname, recursion,
+ dict_ldap->parser->name, attr, vals[0]);
dict_errno = DICT_ERR_RETRY;
}
ldap_value_free(vals);
char *myname = "dict_ldap_lookup";
DICT_LDAP *dict_ldap = (DICT_LDAP *) dict;
LDAPMessage *res = 0;
+ static VSTRING *base;
+ static VSTRING *query;
static VSTRING *result;
struct timeval tv;
- VSTRING *escaped_name = 0,
- *filter_buf = 0;
int rc = 0;
int sizelimit;
- char *sub,
- *end;
dict_errno = 0;
* addresses in domains on the list. This can significantly reduce the
* load on the LDAP server.
*/
- if (dict_ldap->domain) {
- const char *p = strrchr(name, '@');
-
- if (p == 0 || p == name ||
- match_list_match(dict_ldap->domain, ++p) == 0) {
- if (msg_verbose)
- msg_info("%s: domain of %s not found in domain list", myname,
- name);
- return (0);
- }
+ if (db_common_check_domain(dict_ldap->domain, name) == 0) {
+ if (msg_verbose)
+ msg_info("%s: Skipping lookup of '%s'", myname, name);
+ return (0);
}
- /*
- * Initialize the result holder.
- */
- if (result == 0)
- result = vstring_alloc(2);
- vstring_strcpy(result, "");
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
+
+ INIT_VSTR(base, 10);
+ INIT_VSTR(query, 10);
+ INIT_VSTR(result, 10);
/*
* Because the connection may be shared and invalidated via queries for
if (msg_verbose)
msg_info
("%s: No existing connection for LDAP source %s, reopening",
- myname, dict_ldap->ldapsource);
+ myname, dict_ldap->parser->name);
dict_ldap_connect(dict_ldap);
return (0);
} else if (msg_verbose)
msg_info("%s: Using existing connection for LDAP source %s",
- myname, dict_ldap->ldapsource);
+ myname, dict_ldap->parser->name);
/*
* Connection caching, means that the connection handle may have the
if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT, &sizelimit)
!= LDAP_OPT_SUCCESS)
msg_warn("%s: %s: Unable to set query result size limit to %ld.",
- myname, dict_ldap->ldapsource, dict_ldap->size_limit);
+ myname, dict_ldap->parser->name, dict_ldap->size_limit);
/*
- * Prepare the query.
+ * Expand the search base and query. Skip lookup when the
+ * input key lacks sufficient domain components to satisfy
+ * all the requested %-substitutions.
+ *
+ * When the search base is not static, LDAP_NO_SUCH_OBJECT is
+ * expected and is therefore treated as a non-error: the lookup
+ * returns no results rather than a soft error.
*/
- tv.tv_sec = dict_ldap->timeout;
- tv.tv_usec = 0;
- escaped_name = vstring_alloc(20);
- filter_buf = vstring_alloc(30);
+ if (!db_common_expand(dict_ldap->ctx, dict_ldap->search_base,
+ name, 0, base, rfc2253_quote)) {
+ if (msg_verbose > 1)
+ msg_info("%s: %s: Empty expansion for %s", myname,
+ dict_ldap->parser->name, dict_ldap->search_base);
+ return (0);
+ }
- /*
- * If any characters in the supplied address should be escaped per RFC
- * 2254, do so. Thanks to Keith Stevenson and Wietse. And thanks to
- * Samuel Tardieu for spotting that wildcard searches were being done in
- * the first place, which prompted the ill-conceived lookup_wildcards
- * parameter and then this more comprehensive mechanism.
- */
- end = (char *) name + strlen((char *) name);
- sub = (char *) strpbrk((char *) name, "*()\\\0");
- if (sub && sub != end) {
- if (msg_verbose)
- msg_info("%s: Found character(s) in %s that must be escaped",
- myname, name);
- for (sub = (char *) name; sub != end; sub++) {
- switch (*sub) {
- case '*':
- vstring_strcat(escaped_name, "\\2a");
- break;
- case '(':
- vstring_strcat(escaped_name, "\\28");
- break;
- case ')':
- vstring_strcat(escaped_name, "\\29");
- break;
- case '\\':
- vstring_strcat(escaped_name, "\\5c");
- break;
- case '\0':
- vstring_strcat(escaped_name, "\\00");
- break;
- default:
- vstring_strncat(escaped_name, sub, 1);
- }
- }
- if (msg_verbose)
- msg_info("%s: After escaping, it's %s", myname,
- vstring_str(escaped_name));
- } else
- vstring_strcpy(escaped_name, (char *) name);
+ if (!db_common_expand(dict_ldap->ctx, dict_ldap->query,
+ name, 0, query, rfc2254_quote)) {
+ if (msg_verbose > 1)
+ msg_info("%s: %s: Empty expansion for %s", myname,
+ dict_ldap->parser->name, dict_ldap->query);
+ return (0);
+ }
/*
- * Does the supplied query_filter even include a substitution?
+ * Prepare the query.
*/
- if ((char *) strchr(dict_ldap->query_filter, '%') == NULL) {
-
- /*
- * No, log the fact and continue.
- */
- msg_warn("%s: %s: Fixed query_filter %s is probably useless",
- myname, dict_ldap->ldapsource, dict_ldap->query_filter);
- vstring_strcpy(filter_buf, dict_ldap->query_filter);
- } else {
- dict_ldap_expand_filter(dict_ldap->ldapsource, dict_ldap->query_filter,
- vstring_str(escaped_name), filter_buf);
- }
+ tv.tv_sec = dict_ldap->timeout;
+ tv.tv_usec = 0;
/*
* On to the search.
*/
if (msg_verbose)
- msg_info("%s: Searching with filter %s", myname,
- vstring_str(filter_buf));
+ msg_info("%s: %s: Searching with filter %s", myname,
+ dict_ldap->parser->name, vstring_str(query));
- rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base,
- dict_ldap->scope,
- vstring_str(filter_buf),
+ rc = ldap_search_st(dict_ldap->ld, vstring_str(base),
+ dict_ldap->scope, vstring_str(query),
dict_ldap->result_attributes->argv,
0, &tv, &res);
if (rc == LDAP_SERVER_DOWN) {
if (msg_verbose)
msg_info("%s: Lost connection for LDAP source %s, reopening",
- myname, dict_ldap->ldapsource);
+ myname, dict_ldap->parser->name);
ldap_unbind(dict_ldap->ld);
dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0;
if (dict_errno)
return (0);
- rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base,
- dict_ldap->scope,
- vstring_str(filter_buf),
+ rc = ldap_search_st(dict_ldap->ld, vstring_str(base),
+ dict_ldap->scope, vstring_str(query),
dict_ldap->result_attributes->argv,
0, &tv, &res);
}
- if (rc == LDAP_SUCCESS) {
+ switch (rc) {
+
+ case LDAP_SUCCESS:
/*
* Search worked; extract the requested result_attribute.
*/
- dict_ldap_get_values(dict_ldap, res, result);
+ dict_ldap_get_values(dict_ldap, res, result, name);
/*
* OpenLDAP's ldap_next_attribute returns a bogus
msg_info("%s: Search returned %s", myname,
VSTRING_LEN(result) >
0 ? vstring_str(result) : "nothing");
- } else {
+ break;
+ case LDAP_NO_SUCH_OBJECT:
+ /*
+ * If the search base is input key dependent, then not finding it,
+ * is equivalent to not finding the input key. Sadly, we cannot
+ * detect misconfiguration in this case.
+ */
+ if (dict_ldap->dynamic_base)
+ break;
+
+ msg_warn("%s: %s: Search base '%s' not found: %d: %s",
+ myname, dict_ldap->parser->name,
+ vstring_str(base), rc, ldap_err2string(rc));
+ dict_errno = DICT_ERR_RETRY;
+ break;
+
+ default:
/*
* Rats. The search didn't work.
*/
* And tell the caller to try again later.
*/
dict_errno = DICT_ERR_RETRY;
+ break;
}
/*
*/
if (res != 0)
ldap_msgfree(res);
- if (filter_buf != 0)
- vstring_free(filter_buf);
- if (escaped_name != 0)
- vstring_free(escaped_name);
/*
* If we had an error, return nothing, Otherwise, return the result, if
if (conn->conn_ld) {
if (msg_verbose)
msg_info("%s: Closed connection handle for LDAP source %s",
- myname, dict_ldap->ldapsource);
+ myname, dict_ldap->parser->name);
ldap_unbind(conn->conn_ld);
}
binhash_delete(conn_hash, ht->key, ht->key_len, myfree);
}
cfg_parser_free(dict_ldap->parser);
- myfree(dict_ldap->ldapsource);
myfree(dict_ldap->server_host);
myfree(dict_ldap->search_base);
if (dict_ldap->domain)
- match_list_free(dict_ldap->domain);
- myfree(dict_ldap->query_filter);
- if (dict_ldap->result_filter)
- myfree(dict_ldap->result_filter);
+ string_list_free(dict_ldap->domain);
+ myfree(dict_ldap->query);
+ if (dict_ldap->result_format)
+ myfree(dict_ldap->result_format);
argv_free(dict_ldap->result_attributes);
myfree(dict_ldap->bind_dn);
myfree(dict_ldap->bind_pw);
+ if (dict_ldap->ctx)
+ db_common_free_ctx(dict_ldap->ctx);
#ifdef LDAP_API_FEATURE_X_OPENLDAP
myfree(dict_ldap->tls_ca_cert_file);
myfree(dict_ldap->tls_ca_cert_dir);
dict_ldap->ld = NULL;
dict_ldap->parser = cfg_parser_alloc(ldapsource);
- dict_ldap->ldapsource = mystrdup(ldapsource);
server_host = cfg_get_str(dict_ldap->parser, "server_host",
"localhost", 1, 0);
if (strcasecmp(url_desc->lud_scheme, "ldaps") == 0)
dict_ldap->ldap_ssl = 1;
ldap_free_urldesc(url_desc);
- vstring_sprintf_append(url_list, " %s", h);
+ if (VSTRING_LEN(url_list) > 0)
+ VSTRING_ADDCH(url_list, ' ');
+ vstring_strcat(url_list, h);
} else {
+ if (VSTRING_LEN(url_list) > 0)
+ VSTRING_ADDCH(url_list, ' ');
if (strrchr(h, ':'))
- vstring_sprintf_append(url_list, " ldap://%s", h);
+ vstring_sprintf_append(url_list, "ldap://%s", h);
else
- vstring_sprintf_append(url_list, " ldap://%s:%d", h,
+ vstring_sprintf_append(url_list, "ldap://%s:%d", h,
dict_ldap->server_port);
}
#else
- vstring_sprintf_append(url_list, " %s", h);
+ if (VSTRING_LEN(url_list) > 0)
+ VSTRING_ADDCH(url_list, ' ');
+ vstring_strcat(url_list, h);
#endif
}
- dict_ldap->server_host =
- mystrdup(VSTRING_LEN(url_list) > 0 ? vstring_str(url_list) + 1 : "");
+ VSTRING_TERMINATE(url_list);
+ dict_ldap->server_host = vstring_export(url_list);
#if defined(LDAP_API_FEATURE_X_OPENLDAP)
dict_ldap->server_host);
#endif
myfree(server_host);
- vstring_free(url_list);
/*
* Scope handling thanks to Carsten Hoeger of SuSE.
domainlist = cfg_get_str(dict_ldap->parser, "domain", "", 0, 0);
if (*domainlist) {
-#ifdef MATCH_FLAG_NONE
- dict_ldap->domain = match_list_init(MATCH_FLAG_NONE,
- domainlist, 1, match_string);
-#else
- dict_ldap->domain = match_list_init(domainlist, 1, match_string);
-#endif
+ dict_ldap->domain = string_list_init(MATCH_FLAG_NONE, domainlist);
if (dict_ldap->domain == NULL)
- msg_warn("%s: domain match list creation using \"%s\" failed, will continue without it",
- myname, domainlist);
- if (msg_verbose)
- msg_info("%s: domain list created using \"%s\"", myname,
- domainlist);
+ /*
+ * The "domain" optimization skips input keys that may in fact
+ * have unwanted matches in the database, so failure to create
+ * the match list is fatal.
+ */
+ msg_fatal("%s: %s: domain match list creation using '%s' failed",
+ myname, ldapsource, domainlist);
} else {
dict_ldap->domain = NULL;
}
* Thanks to Manuel Guesdon for spotting that this wasn't really getting
* set.
*/
- dict_ldap->timeout = cfg_get_int(dict_ldap->parser, "timeout",
- 10, 0, 0);
+ dict_ldap->timeout = cfg_get_int(dict_ldap->parser, "timeout", 10, 0, 0);
- dict_ldap->query_filter =
- cfg_get_str(dict_ldap->parser, "query_filter",
- "(mailacceptinggeneralid=%s)", 0, 0);
+#if 0 /* No benefit from changing this to match the MySQL/PGSQL syntax */
+ if ((dict_ldap->query =
+ cfg_get_str(dict_ldap->parser, "query", 0, 0, 0)) == 0)
+#endif
+ dict_ldap->query =
+ cfg_get_str(dict_ldap->parser, "query_filter",
+ "(mailacceptinggeneralid=%s)", 0, 0);
- dict_ldap->result_filter =
- cfg_get_str(dict_ldap->parser, "result_filter", "%s", 0, 0);
+ if ((dict_ldap->result_format =
+ cfg_get_str(dict_ldap->parser, "result_format", 0, 0, 0)) == 0)
+ dict_ldap->result_format =
+ cfg_get_str(dict_ldap->parser, "result_filter", "%s", 1, 0);
- if (strcmp(dict_ldap->result_filter, "%s") == 0) {
- myfree(dict_ldap->result_filter);
- dict_ldap->result_filter = NULL;
+ /*
+ * Must parse all templates before we can use db_common_expand()
+ * If data dependent substitutions are found in the search base,
+ * treat NO_SUCH_OBJECT search errors as a non-matching key, rather
+ * than a fatal run-time error.
+ */
+ dict_ldap->ctx = 0;
+ dict_ldap->dynamic_base =
+ db_common_parse(&dict_ldap->dict, &dict_ldap->ctx,
+ dict_ldap->search_base, 1);
+ if (!db_common_parse(0, &dict_ldap->ctx, dict_ldap->query, 1)) {
+ msg_warn("%s: %s: Fixed query_filter %s is probably useless",
+ myname, ldapsource, dict_ldap->query);
}
+ (void) db_common_parse(0, &dict_ldap->ctx, dict_ldap->result_format, 0);
+
attr = cfg_get_str(dict_ldap->parser, "result_attribute",
"maildrop", 0, 0);
dict_ldap->result_attributes = argv_split(attr, " ,\t\r\n");
attr = cfg_get_str(dict_ldap->parser, "special_result_attribute",
"", 0, 0);
- if (*attr) {
+ if (*attr)
argv_split_append(dict_ldap->result_attributes, attr, " ,\t\r\n");
- }
myfree(attr);
/*
dict_ldap->bind_pw = cfg_get_str(dict_ldap->parser, "bind_pw", "", 0, 0);
/*
- * get configured value of "cache"; default to false
+ * LDAP message caching never worked and is no longer supported.
*/
tmp = cfg_get_bool(dict_ldap->parser, "cache", 0);
if (tmp)
msg_warn("%s: %s ignoring cache", myname, ldapsource);
- /*
- * get configured value of "cache_expiry"; default to 30 seconds
- */
tmp = cfg_get_int(dict_ldap->parser, "cache_expiry", -1, 0, 0);
if (tmp >= 0)
msg_warn("%s: %s ignoring cache_expiry", myname, ldapsource);
- /*
- * get configured value of "cache_size"; default to 32k
- */
tmp = cfg_get_int(dict_ldap->parser, "cache_size", -1, 0, 0);
if (tmp >= 0)
msg_warn("%s: %s ignoring cache_size", myname, ldapsource);
- /*
- * get configured value of "recursion_limit"; default to 1000
- */
dict_ldap->recursion_limit = cfg_get_int(dict_ldap->parser,
"recursion_limit", 1000, 1, 0);
/*
- * get configured value of "expansion_limit"; default to 0
+ * XXX: The default should be non-zero for safety, but that is not
+ * backwards compatible.
*/
dict_ldap->expansion_limit = cfg_get_int(dict_ldap->parser,
"expansion_limit", 0, 0, 0);
- /*
- * get configured value of "size_limit"; default to expansion_limit
- */
dict_ldap->size_limit = cfg_get_int(dict_ldap->parser, "size_limit",
- dict_ldap->expansion_limit,
- 0, 0);
+ dict_ldap->expansion_limit, 0, 0);
/*
* Alias dereferencing suggested by Mike Mattice.
/* Password for the above.
/* .IP \fIdbname\fR
/* Name of the database.
+/* .IP \fIdomain\fR
+/* List of domains the queries should be restricted to. If
+/* specified, only FQDN addresses whose domain parts matching this
+/* list will be queried against the SQL database. Lookups for
+/* partial addresses are also supressed. This can significantly
+/* reduce the query load on the server.
+/* .IP \fIquery\fR
+/* Query template, before the query is actually issued, variable
+/* substitutions are performed. See mysql_table(5) for details. If
+/* No query is specified, the legacy variables \fItable\fR,
+/* \fIselect_field\fR, \fIwhere_field\fR and \fIadditional_conditions\fR
+/* are used to construct the query template.
+/* .IP \fIresult_format\fR
+/* The format used to expand results from queries. Substitutions
+/* are performed as described in mysql_table(5). Defaults to returning
+/* the lookup result unchanged.
+/* .IP expansion_limit
+/* Limit (if any) on the total number of lookup result values. Lookups which
+/* exceed the limit fail with dict_errno=DICT_ERR_RETRY. Note that each
+/* non-empty (and non-NULL) column of a multi-column result row counts as
+/* one result.
/* .IP \fItable\fR
-/* Name of the table.
+/* When \fIquery\fR is not set, name of the table used to construct the
+/* query string. This provides compatibility with older releases.
/* .IP \fIselect_field\fR
-/* Name of the result field.
+/* When \fIquery\fR is not set, name of the result field used to
+/* construct the query string. This provides compatibility with older
+/* releases.
/* .IP \fIwhere_field\fR
-/* Field used in the WHERE clause.
+/* When \fIquery\fR is not set, name of the where clause field used to
+/* construct the query string. This provides compatibility with older
+/* releases.
/* .IP \fIadditional_conditions\fR
-/* Additional conditions to the WHERE clause.
+/* When \fIquery\fR is not set, additional where clause conditions used
+/* to construct the query string. This provides compatibility with older
+/* releases.
+/* .IP \fIhosts\fR
+/* List of hosts to connect to.
+/* .PP
+/* For example, if you want the map to reference databases of
+/* the name "your_db" and execute a query like this: select
+/* forw_addr from aliases where alias like '<some username>'
+/* against any database called "vmailer_info" located on hosts
+/* host1.some.domain and host2.some.domain, logging in as user
+/* "vmailer" and password "passwd" then the configuration file
+/* should read:
+/* .PP
+/* \fIuser\fR = \fBvmailer\fR
+/* .br
+/* \fIpassword\fR = \fBpasswd\fR
+/* .br
+/* \fIdbname\fR = \fBvmailer_info\fR
+/* .br
+/* \fItable\fR = \fBaliases\fR
+/* .br
+/* \fIselect_field\fR = \fBforw_addr\fR
+/* .br
+/* \fIwhere_field\fR = \fBalias\fR
+/* .br
+/* \fIhosts\fR = \fBhost1.some.domain\fR \fBhost2.some.domain\fR
+/* .IP \fIadditional_conditions\fR
+/* Backward compatibility when \fIquery\fR is not set, additional
+/* conditions to the WHERE clause.
/* .IP \fIhosts\fR
/* List of hosts to connect to.
/* .PP
/* Global library. */
#include "cfg_parser.h"
+#include "db_common.h"
/* Application-specific. */
} PLMYSQL;
typedef struct {
+ DICT dict;
CFG_PARSER *parser;
+ char *query;
+ char *result_format;
+ STRING_LIST *domain;
+ void *ctx;
+ int expansion_limit;
char *username;
char *password;
char *dbname;
- char *table;
- char *select_field;
- char *where_field;
- char *additional_conditions;
- char **hostnames;
- int len_hosts;
-} MYSQL_NAME;
-
-typedef struct {
- DICT dict;
+ ARGV *hosts;
PLMYSQL *pldb;
- MYSQL_NAME *name;
} DICT_MYSQL;
#define STATACTIVE (1<<0)
#define IDLE_CONN_INTV 60 /* 1 minute */
/* internal function declarations */
-static PLMYSQL *plmysql_init(char *hostnames[], int);
+static PLMYSQL *plmysql_init(ARGV *);
static MYSQL_RES *plmysql_query(PLMYSQL *, const char *, char *, char *, char *);
static void plmysql_dealloc(PLMYSQL *);
static void plmysql_close_host(HOST *);
static const char *dict_mysql_lookup(DICT *, const char *);
DICT *dict_mysql_open(const char *, int, int);
static void dict_mysql_close(DICT *);
-static MYSQL_NAME *mysqlname_parse(const char *);
+static void mysql_parse_config(DICT_MYSQL *, const char *);
static HOST *host_init(const char *);
+/* dict_mysql_quote - escape SQL metacharacters in input string */
+
+static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result)
+{
+ DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
+ int len = strlen(name);
+ int buflen = 2*len + 1;
+
+ /*
+ * We won't get integer overflows in 2*len + 1, because Postfix
+ * input keys have reasonable size limits, better safe than sorry.
+ */
+ if (buflen < len)
+ msg_panic("dict_mysql_quote: integer overflow in 2*%d+1", len);
+ VSTRING_SPACE(result, buflen);
+
+ /*
+ * XXX Too expensive to find out which connection is still open at
+ * this point. Grrr!
+ */
+#if 0 && defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ mysql_real_escape_string(dict_mysql->pldb->db_hosts[i].db,
+ vstring_end(result), name, len);
+#else
+ mysql_escape_string(vstring_end(result), name, len);
+#endif
+ VSTRING_SKIP(result);
+}
+/* dict_mysql_lookup - find database entry */
-/**********************************************************************
- * public interface dict_mysql_lookup
- * find database entry return 0 if no alias found, set dict_errno
- * on errors to DICT_ERRBO_RETRY and set dict_errno to 0 on success
- *********************************************************************/
static const char *dict_mysql_lookup(DICT *dict, const char *name)
{
+ char *myname = "dict_mysql_lookup";
+ DICT_MYSQL *dict_mysql = (DICT_MYSQL *)dict;
+ PLMYSQL *pldb = dict_mysql->pldb;
MYSQL_RES *query_res;
MYSQL_ROW row;
- DICT_MYSQL *dict_mysql;
- PLMYSQL *pldb;
static VSTRING *result;
- static VSTRING *query = 0;
- int i,
- j,
- numrows;
- char *name_escaped = 0;
-
- dict_mysql = (DICT_MYSQL *) dict;
- pldb = dict_mysql->pldb;
- /* initialization for query */
- query = vstring_alloc(24);
- vstring_strcpy(query, "");
- if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) {
- msg_fatal("dict_mysql_lookup: out of memory.");
+ static VSTRING *query;
+ int i;
+ int j;
+ int numrows;
+ int expansion;
+ const char *r;
+
+ dict_errno = 0;
+
+ /*
+ * If there is a domain list for this map, then only search for
+ * addresses in domains on the list. This can significantly reduce
+ * the load on the server. Do not try "@domain" keys.
+ */
+ if (db_common_check_domain(dict_mysql->domain, name) == 0) {
+ if (msg_verbose)
+ msg_info("%s: Skipping lookup of '%s'", myname, name);
+ return (0);
}
- /* prepare the query */
- mysql_escape_string(name_escaped, name, (unsigned int) strlen(name));
- vstring_sprintf(query, "select %s from %s where %s = '%s' %s", dict_mysql->name->select_field,
- dict_mysql->name->table, dict_mysql->name->where_field, name_escaped,
- dict_mysql->name->additional_conditions);
- if (msg_verbose)
- msg_info("dict_mysql_lookup using sql query: %s", vstring_str(query));
- /* free mem associated with preparing the query */
- myfree(name_escaped);
+
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
+
+ INIT_VSTR(query, 10);
+
+ /*
+ * Suppress the lookup if the query expansion is empty
+ */
+ if (!db_common_expand(dict_mysql->ctx, dict_mysql->query,
+ name, 0, query, dict_mysql_quote))
+ return (0);
+
/* do the query - set dict_errno & cleanup if there's an error */
- if ((query_res = plmysql_query(pldb,
- vstring_str(query),
- dict_mysql->name->dbname,
- dict_mysql->name->username,
- dict_mysql->name->password)) == 0) {
+ if ((query_res = plmysql_query(pldb, vstring_str(query),
+ dict_mysql->dbname,
+ dict_mysql->username,
+ dict_mysql->password)) == 0) {
dict_errno = DICT_ERR_RETRY;
- vstring_free(query);
- return 0;
+ return (0);
}
- dict_errno = 0;
- /* free the vstring query */
- vstring_free(query);
+
numrows = mysql_num_rows(query_res);
if (msg_verbose)
- msg_info("dict_mysql_lookup: retrieved %d rows", numrows);
+ msg_info("%s: retrieved %d rows", myname, numrows);
if (numrows == 0) {
mysql_free_result(query_res);
return 0;
}
- if (result == 0)
- result = vstring_alloc(10);
- vstring_strcpy(result, "");
- for (i = 0; i < numrows; i++) {
+
+ INIT_VSTR(result, 10);
+
+ for (expansion = i = 0; i < numrows && dict_errno == 0; i++) {
row = mysql_fetch_row(query_res);
- if (i > 0)
- vstring_strcat(result, ",");
for (j = 0; j < mysql_num_fields(query_res); j++) {
- if (row[j] == 0) {
- if (msg_verbose > 1)
- msg_info("dict_mysql_lookup: null field #%d row #%d", j, i);
- mysql_free_result(query_res);
- return (0);
+ if (db_common_expand(dict_mysql->ctx, dict_mysql->result_format,
+ row[j], name, result, 0)
+ && dict_mysql->expansion_limit > 0
+ && ++expansion > dict_mysql->expansion_limit) {
+ msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
+ myname, dict_mysql->parser->name, name);
+ dict_errno = DICT_ERR_RETRY;
+ break;
}
- if (j > 0)
- vstring_strcat(result, ",");
- vstring_strcat(result, row[j]);
- if (msg_verbose > 1)
- msg_info("dict_mysql_lookup: retrieved field: %d: %s", j, row[j]);
}
}
mysql_free_result(query_res);
- return vstring_str(result);
+ r = vstring_str(result);
+ return ((dict_errno == 0 && *r) ? r : 0);
}
/* dict_mysql_check_stat - check the status of a host */
event_cancel_timer(dict_mysql_event, (char *) host);
}
-/**********************************************************************
- * public interface dict_mysql_open
- * create association with database with appropriate values
- * parse the map's config file
- * allocate memory
- **********************************************************************/
+/* mysql_parse_config - parse mysql configuration file */
+
+static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
+{
+ const char *myname = "mysqlname_parse";
+ CFG_PARSER *p;
+ VSTRING *buf;
+ int i;
+ char *hosts;
+ char *domain;
+
+ p = dict_mysql->parser = cfg_parser_alloc(mysqlcf);
+ dict_mysql->username = cfg_get_str(p, "user", "", 0, 0);
+ dict_mysql->password = cfg_get_str(p, "password", "", 0, 0);
+ dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
+ dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
+ /*
+ * XXX: The default should be non-zero for safety, but that is not
+ * backwards compatible.
+ */
+ dict_mysql->expansion_limit = cfg_get_int(dict_mysql->parser,
+ "expansion_limit", 0, 0, 0);
+
+ if ((dict_mysql->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) {
+ /*
+ * No query specified -- fallback to building it from components
+ * (old style "select %s from %s where %s")
+ */
+ buf = vstring_alloc(64);
+ db_common_sql_build_query(buf, p);
+ dict_mysql->query = vstring_export(buf);
+ }
+
+ /*
+ * Must parse all templates before we can use db_common_expand()
+ */
+ dict_mysql->ctx = 0;
+ (void) db_common_parse(&dict_mysql->dict, &dict_mysql->ctx,
+ dict_mysql->query, 1);
+ (void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format, 0);
+
+ domain = cfg_get_str(p, "domain", "", 0, 0);
+ if (*domain) {
+ if (!(dict_mysql->domain = string_list_init(MATCH_FLAG_NONE, domain)))
+ /*
+ * The "domain" optimization skips input keys that may in fact
+ * have unwanted matches in the database, so failure to create
+ * the match list is fatal.
+ */
+ msg_fatal("%s: %s: domain match list creation using '%s' failed",
+ myname, mysqlcf, domain);
+ }
+ else
+ dict_mysql->domain = 0;
+ myfree(domain);
+
+ hosts = cfg_get_str(p, "hosts", "", 0, 0);
+
+ dict_mysql->hosts = argv_split(hosts, " ,\t\r\n");
+ if (dict_mysql->hosts->argc == 0) {
+ argv_add(dict_mysql->hosts, "localhost", ARGV_END);
+ argv_terminate(dict_mysql->hosts);
+ if (msg_verbose)
+ msg_info("%s: %s: no hostnames specified, defaulting to '%s'",
+ myname, mysqlcf, dict_mysql->hosts->argv[0]);
+ }
+ myfree(hosts);
+}
+
+/* dict_mysql_open - open MYSQL data base */
+
DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags)
{
DICT_MYSQL *dict_mysql;
dict_mysql->dict.lookup = dict_mysql_lookup;
dict_mysql->dict.close = dict_mysql_close;
dict_mysql->dict.flags = dict_flags | DICT_FLAG_FIXED;
- dict_mysql->name = mysqlname_parse(name);
- dict_mysql->pldb = plmysql_init(dict_mysql->name->hostnames,
- dict_mysql->name->len_hosts);
+ mysql_parse_config(dict_mysql, name);
+ dict_mysql->pldb = plmysql_init(dict_mysql->hosts);
if (dict_mysql->pldb == NULL)
msg_fatal("couldn't intialize pldb!\n");
return (DICT_DEBUG (&dict_mysql->dict));
}
-/* mysqlname_parse - parse mysql configuration file */
-static MYSQL_NAME *mysqlname_parse(const char *mysqlcf)
-{
- const char *myname = "mysqlname_parse";
- int i;
- char *hosts;
- MYSQL_NAME *name = (MYSQL_NAME *) mymalloc(sizeof(MYSQL_NAME));
- ARGV *hosts_argv;
-
- /* parser */
- name->parser = cfg_parser_alloc(mysqlcf);
-
- /* username */
- name->username = cfg_get_str(name->parser, "user", "", 0, 0);
-
- /* password */
- name->password = cfg_get_str(name->parser, "password", "", 0, 0);
-
- /* database name */
- name->dbname = cfg_get_str(name->parser, "dbname", "", 1, 0);
-
- /* table name */
- name->table = cfg_get_str(name->parser, "table", "", 1, 0);
-
- /* select field */
- name->select_field = cfg_get_str(name->parser, "select_field", "", 1, 0);
-
- /* where field */
- name->where_field = cfg_get_str(name->parser, "where_field", "", 1, 0);
-
- /* additional conditions */
- name->additional_conditions = cfg_get_str(name->parser,
- "additional_conditions",
- "", 0, 0);
-
- /* mysql server hosts */
- hosts = cfg_get_str(name->parser, "hosts", "", 0, 0);
-
- /* coo argv interface */
- hosts_argv = argv_split(hosts, " ,\t\r\n");
- if (hosts_argv->argc == 0) { /* no hosts specified,
- * default to 'localhost' */
- if (msg_verbose)
- msg_info("%s: %s: no hostnames specified, defaulting to 'localhost'",
- myname, mysqlcf);
- argv_add(hosts_argv, "localhost", ARGV_END);
- argv_terminate(hosts_argv);
- }
- name->len_hosts = hosts_argv->argc;
- name->hostnames = (char **) mymalloc((sizeof(char *)) * name->len_hosts);
- i = 0;
- for (i = 0; hosts_argv->argv[i] != NULL; i++) {
- name->hostnames[i] = mystrdup(hosts_argv->argv[i]);
- if (msg_verbose)
- msg_info("%s: %s: adding host '%s' to list of mysql server hosts",
- myname, mysqlcf, name->hostnames[i]);
- }
- myfree(hosts);
- argv_free(hosts_argv);
- return name;
-}
-
-
/*
* plmysql_init - initalize a MYSQL database.
* Return NULL on failure, or a PLMYSQL * on success.
*/
-static PLMYSQL *plmysql_init(char *hostnames[], int len_hosts)
+static PLMYSQL *plmysql_init(ARGV *hosts)
{
PLMYSQL *PLDB;
int i;
- if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == NULL) {
+ if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == 0)
msg_fatal("mymalloc of pldb failed");
- }
- PLDB->len_hosts = len_hosts;
- if ((PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * len_hosts)) == NULL)
- return NULL;
- for (i = 0; i < len_hosts; i++) {
- PLDB->db_hosts[i] = host_init(hostnames[i]);
- }
+
+ PLDB->len_hosts = hosts->argc;
+ if ((PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc)) == 0)
+ return (0);
+ for (i = 0; i < hosts->argc; i++)
+ PLDB->db_hosts[i] = host_init(hosts->argv[i]);
+
return PLDB;
}
return host;
}
-/**********************************************************************
- * public interface dict_mysql_close
- * unregister, disassociate from database, freeing appropriate memory
- **********************************************************************/
+/* dict_mysql_close - close MYSQL database */
+
static void dict_mysql_close(DICT *dict)
{
int i;
DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
plmysql_dealloc(dict_mysql->pldb);
- cfg_parser_free(dict_mysql->name->parser);
- myfree(dict_mysql->name->username);
- myfree(dict_mysql->name->password);
- myfree(dict_mysql->name->dbname);
- myfree(dict_mysql->name->table);
- myfree(dict_mysql->name->select_field);
- myfree(dict_mysql->name->where_field);
- myfree(dict_mysql->name->additional_conditions);
- for (i = 0; i < dict_mysql->name->len_hosts; i++) {
- myfree(dict_mysql->name->hostnames[i]);
- }
- myfree((char *) dict_mysql->name->hostnames);
- myfree((char *) dict_mysql->name);
+ cfg_parser_free(dict_mysql->parser);
+ myfree(dict_mysql->username);
+ myfree(dict_mysql->password);
+ myfree(dict_mysql->dbname);
+ myfree(dict_mysql->query);
+ myfree(dict_mysql->result_format);
+ if (dict_mysql->domain)
+ string_list_free(dict_mysql->domain);
+ if (dict_mysql->hosts)
+ argv_free(dict_mysql->hosts);
+ if (dict_mysql->ctx)
+ db_common_free_ctx(dict_mysql->ctx);
dict_free(dict);
}
/* Password for the above.
/* .IP \fIdbname\fR
/* Name of the database.
+/* .IP \fIquery\fR
+/* Query template. If not defined a default query template is constructed
+/* from the legacy \fIselect_function\fR or failing that the \fItable\fR,
+/* \fIselect_field\fR, \fIwhere_field\fR, and \fIadditional_conditions\fR
+/* parameters. Before the query is issues, variable substitutions are
+/* performed. See pgsql_table(5).
+/* .IP \fIdomain\fR
+/* List of domains the queries should be restricted to. If
+/* specified, only FQDN addresses whose domain parts matching this
+/* list will be queried against the SQL database. Lookups for
+/* partial addresses are also supressed. This can significantly
+/* reduce the query load on the server.
+/* .IP \fIresult_format\fR
+/* The format used to expand results from queries. Substitutions
+/* are performed as described in pgsql_table(5). Defaults to returning
+/* the lookup result unchanged.
+/* .IP expansion_limit
+/* Limit (if any) on the total number of lookup result values. Lookups which
+/* exceed the limit fail with dict_errno=DICT_ERR_RETRY. Note that each
+/* non-empty (and non-NULL) column of a multi-column result row counts as
+/* one result.
+/* .IP \fIselect_function\fR
+/* When \fIquery\fR is not defined, the function to be used instead of
+/* the default query based on the legacy \fItable\fR, \fIselect_field\fR,
+/* \fIwhere_field\fR, and \fIadditional_conditions\fR parameters.
/* .IP \fItable\fR
-/* Name of the table.
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* FROM table used to construct the default query template, see pgsql_table(5).
/* .IP \fIselect_field\fR
-/* Name of the result field.
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* SELECT field used to construct the default query template, see pgsql_table(5).
/* .IP \fIwhere_field\fR
-/* Field used in the WHERE clause.
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* WHERE field used to construct the default query template, see pgsql_table(5).
/* .IP \fIadditional_conditions\fR
-/* Additional conditions to the WHERE clause.
-/* .IP \fIquery\fR
-/* Query overriding \fItable\fR, \fIselect_field\fR,
-/* \fIwhere_field\fR, and \fIadditional_conditions\fR. Before the
-/* query is actually issued, all occurrences of %s are replaced
-/* with the address to look up, %u are replaced with the user
-/* portion, and %d with the domain portion.
-/* .IP \fIselect_function\fR
-/* Function to be used instead of the SELECT statement. Overrides
-/* both \fIquery\fR and \fItable\fR, \fIselect_field\fR,
-/* \fIwhere_field\fR, and \fIadditional_conditions\fR settings.
+/* When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
+/* additional text to add to the WHERE field in the default query template (this
+/* usually begins with "and") see pgsql_table(5).
/* .IP \fIhosts\fR
/* List of hosts to connect to.
/* .PP
/* Global library. */
#include "cfg_parser.h"
+#include "db_common.h"
/* Application-specific. */
} PLPGSQL;
typedef struct {
+ DICT dict;
CFG_PARSER *parser;
+ char *query;
+ char *result_format;
+ STRING_LIST *domain;
+ void *ctx;
+ int expansion_limit;
char *username;
char *password;
char *dbname;
char *table;
- char *query; /* if set, overrides fields, etc */
- char *select_function;
- char *select_field;
- char *where_field;
- char *additional_conditions;
- char **hostnames;
- int len_hosts;
-} PGSQL_NAME;
-
-typedef struct {
- DICT dict;
+ ARGV *hosts;
PLPGSQL *pldb;
- PGSQL_NAME *name;
} DICT_PGSQL;
#define PGSQL_RES PGresult
/* internal function declarations */
-static PLPGSQL *plpgsql_init(char *hostnames[], int);
+static PLPGSQL *plpgsql_init(ARGV *);
static PGSQL_RES *plpgsql_query(PLPGSQL *, const char *, char *, char *, char *);
static void plpgsql_dealloc(PLPGSQL *);
static void plpgsql_close_host(HOST *);
static const char *dict_pgsql_lookup(DICT *, const char *);
DICT *dict_pgsql_open(const char *, int, int);
static void dict_pgsql_close(DICT *);
-static PGSQL_NAME *pgsqlname_parse(const char *);
static HOST *host_init(const char *);
+/* dict_pgsql_quote - escape SQL metacharacters in input string */
-/**********************************************************************
- * public interface dict_pgsql_lookup
- * find database entry return 0 if no alias found, set dict_errno
- * on errors to DICT_ERROR_RETRY and set dict_errno to 0 on success
- *********************************************************************/
-static void pgsql_escape_string(char *new, const char *old, unsigned int len)
+static void dict_pgsql_quote(DICT *unused, const char *name, VSTRING *result)
{
- unsigned int x,
- y;
+ const char *sub;
/*
* XXX We really should be using an escaper that is provided by the PGSQL
* library. The code below seems to be over-kill (see RUS-CERT Advisory
* 2001-08:01), but it's better to be safe than to be sorry -- Wietse
*/
- for (x = 0, y = 0; x < len; x++, y++) {
- switch (old[x]) {
+ for (sub = name; *sub; sub++) {
+ switch(*sub) {
case '\n':
- new[y++] = '\\';
- new[y] = 'n';
+ vstring_strcat(result, "\\n");
break;
case '\r':
- new[y++] = '\\';
- new[y] = 'r';
+ vstring_strcat(result, "\\r");
break;
case '\'':
- new[y++] = '\\';
- new[y] = '\'';
+ vstring_strcat(result, "\\'");
break;
case '"':
- new[y++] = '\\';
- new[y] = '"';
+ vstring_strcat(result, "\\\"");
break;
case 0:
- new[y++] = '\\';
- new[y] = '0';
+ vstring_strcat(result, "\\0");
break;
default:
- new[y] = old[x];
+ VSTRING_ADDCH(result, *sub);
break;
}
}
- new[y] = 0;
+ VSTRING_TERMINATE(result);
}
-/*
- * expand a filter (lookup or result)
- */
-static void dict_pgsql_expand_filter(char *filter, char *value, VSTRING *out)
-{
- const char *myname = "dict_pgsql_expand_filter";
- char *sub,
- *end;
-
- /*
- * Yes, replace all instances of %s with the address to look up. Replace
- * %u with the user portion, and %d with the domain portion.
- */
- sub = filter;
- end = sub + strlen(filter);
- while (sub < end) {
-
- /*
- * Make sure it's %[sud] and not something else. For backward
- * compatibilty, treat anything other than %u or %d as %s, with a
- * warning.
- */
- if (*(sub) == '%') {
- char *u = value;
- char *p = strrchr(u, '@');
-
- switch (*(sub + 1)) {
- case 'd':
- if (p)
- vstring_strcat(out, p + 1);
- break;
- case 'u':
- if (p)
- vstring_strncat(out, u, p - u);
- else
- vstring_strcat(out, u);
- break;
- default:
- msg_warn
- ("%s: Invalid filter substitution format '%%%c'!",
- myname, *(sub + 1));
- break;
- case 's':
- vstring_strcat(out, u);
- break;
- }
- sub++;
- } else
- vstring_strncat(out, sub, 1);
- sub++;
- }
-}
+/* dict_pgsql_lookup - find database entry */
static const char *dict_pgsql_lookup(DICT *dict, const char *name)
{
+ char *myname = "dict_pgsql_lookup";
PGSQL_RES *query_res;
DICT_PGSQL *dict_pgsql;
PLPGSQL *pldb;
+ static VSTRING *query;
static VSTRING *result;
- static VSTRING *query = 0;
- int i,
- j,
- numrows;
- char *name_escaped = 0;
- int isFunctionCall;
+ int i;
+ int j;
+ int numrows;
int numcols;
-
+ int expansion;
+ const char *r;
+
dict_pgsql = (DICT_PGSQL *) dict;
pldb = dict_pgsql->pldb;
- /* initialization for query */
- query = vstring_alloc(24);
- vstring_strcpy(query, "");
- if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) {
- msg_fatal("dict_pgsql_lookup: out of memory.");
- }
- /* prepare the query */
- pgsql_escape_string(name_escaped, name, (unsigned int) strlen(name));
-
- /* Build SQL - either a select from table or select a function */
-
- isFunctionCall = (dict_pgsql->name->select_function != NULL);
- if (isFunctionCall) {
- vstring_sprintf(query, "select %s('%s')",
- dict_pgsql->name->select_function,
- name_escaped);
- } else if (dict_pgsql->name->query) {
- dict_pgsql_expand_filter(dict_pgsql->name->query, name_escaped, query);
- } else {
- vstring_sprintf(query, "select %s from %s where %s = '%s' %s",
- dict_pgsql->name->select_field,
- dict_pgsql->name->table,
- dict_pgsql->name->where_field,
- name_escaped,
- dict_pgsql->name->additional_conditions);
- }
- if (msg_verbose)
- msg_info("dict_pgsql_lookup using sql query: %s", vstring_str(query));
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
- /* free mem associated with preparing the query */
- myfree(name_escaped);
+ INIT_VSTR(query, 10);
+ INIT_VSTR(result, 10);
+
+ dict_errno = 0;
+ /*
+ * If there is a domain list for this map, then only search for
+ * addresses in domains on the list. This can significantly reduce
+ * the load on the server. Do not try "@domain" keys.
+ */
+ if (db_common_check_domain(dict_pgsql->domain, name) == 0) {
+ if (msg_verbose)
+ msg_info("%s: Skipping lookup of '%s'", myname, name);
+ return (0);
+ }
+ /*
+ * Suppress the actual lookup if the expansion is empty
+ */
+ if (!db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
+ name, 0, query, dict_pgsql_quote))
+ return (0);
+
/* do the query - set dict_errno & cleanup if there's an error */
- if ((query_res = plpgsql_query(pldb,
- vstring_str(query),
- dict_pgsql->name->dbname,
- dict_pgsql->name->username,
- dict_pgsql->name->password)) == 0) {
+ if ((query_res = plpgsql_query(pldb, vstring_str(query),
+ dict_pgsql->dbname,
+ dict_pgsql->username,
+ dict_pgsql->password)) == 0) {
dict_errno = DICT_ERR_RETRY;
- vstring_free(query);
return 0;
}
- dict_errno = 0;
- /* free the vstring query */
- vstring_free(query);
+
numrows = PQntuples(query_res);
if (msg_verbose)
- msg_info("dict_pgsql_lookup: retrieved %d rows", numrows);
+ msg_info("%s: retrieved %d rows", myname, numrows);
if (numrows == 0) {
PQclear(query_res);
return 0;
}
numcols = PQnfields(query_res);
- if (numcols == 1 && numrows == 1 && isFunctionCall) {
-
- /*
- * We do the above check because PostgreSQL 7.3 will allow functions
- * to return result sets
- */
- if (PQgetisnull(query_res, 0, 0) == 1) {
-
- /*
- * Functions returning a single row & column that is null are
- * deemed to have not found the key.
- */
- PQclear(query_res);
- return 0;
- }
- }
- if (result == 0)
- result = vstring_alloc(10);
-
- vstring_strcpy(result, "");
- for (i = 0; i < numrows; i++) {
- if (i > 0)
- vstring_strcat(result, ",");
+ for (expansion = i = 0; i < numrows && dict_errno == 0; i++) {
for (j = 0; j < numcols; j++) {
- if (j > 0)
- vstring_strcat(result, ",");
- vstring_strcat(result, PQgetvalue(query_res, i, j));
- if (msg_verbose > 1)
- msg_info("dict_pgsql_lookup: retrieved field: %d: %s", j, PQgetvalue(query_res, i, j));
+ r = PQgetvalue(query_res, i, j);
+ if (db_common_expand(dict_pgsql->ctx, dict_pgsql->result_format,
+ r, name, result, 0)
+ && dict_pgsql->expansion_limit > 0
+ && ++expansion > dict_pgsql->expansion_limit) {
+ msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
+ myname, dict_pgsql->parser->name, name);
+ dict_errno = DICT_ERR_RETRY;
+ break;
+ }
}
}
PQclear(query_res);
- return vstring_str(result);
+ r = vstring_str(result);
+ return ((dict_errno == 0 && *r) ? r : 0);
}
/* dict_pgsql_check_stat - check the status of a host */
event_cancel_timer(dict_pgsql_event, (char *) host);
}
-/**********************************************************************
- * public interface dict_pgsql_open
- * create association with database with appropriate values
- * parse the map's config file
- * allocate memory
- **********************************************************************/
-DICT *dict_pgsql_open(const char *name, int open_flags, int dict_flags)
+/* pgsql_parse_config - parse pgsql configuration file */
+
+static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf)
{
- DICT_PGSQL *dict_pgsql;
+ const char *myname = "pgsql_parse_config";
+ CFG_PARSER *p;
+ int i;
+ char *hosts;
+ VSTRING *query;
+ char *select_function;
+ char *domain;
+ p = dict_pgsql->parser = cfg_parser_alloc(pgsqlcf);
+ dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0);
+ dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0);
+ dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
+ dict_pgsql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
/*
- * Sanity checks.
+ * XXX: The default should be non-zero for safety, but that is not
+ * backwards compatible.
*/
+ dict_pgsql->expansion_limit = cfg_get_int(dict_pgsql->parser,
+ "expansion_limit", 0, 0, 0);
+
+ if ((dict_pgsql->query = cfg_get_str(p, "query", 0, 0, 0)) == 0) {
+ /*
+ * No query specified -- fallback to building it from components
+ * ( old style "select %s from %s where %s" )
+ */
+ query = vstring_alloc(64);
+ select_function = cfg_get_str(p, "select_function", 0, 0, 0);
+ if (select_function != 0) {
+ vstring_sprintf(query, "SELECT %s('%%s')", select_function);
+ myfree(select_function);
+ } else
+ db_common_sql_build_query(query, p);
+ dict_pgsql->query = vstring_export(query);
+ }
+
+ /*
+ * Must parse all templates before we can use db_common_expand()
+ */
+ dict_pgsql->ctx = 0;
+ (void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx,
+ dict_pgsql->query, 1);
+ (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0);
+
+ domain = cfg_get_str(p, "domain", "", 0, 0);
+ if (*domain) {
+ if (!(dict_pgsql->domain = string_list_init(MATCH_FLAG_NONE, domain)))
+ /*
+ * The "domain" optimization skips input keys that may in fact
+ * have unwanted matches in the database, so failure to create
+ * the match list is fatal.
+ */
+ msg_fatal("%s: %s: domain match list creation using '%s' failed",
+ myname, pgsqlcf, domain);
+ }
+ else
+ dict_pgsql->domain = 0;
+ myfree(domain);
+
+ hosts = cfg_get_str(p, "hosts", "", 0, 0);
+
+ dict_pgsql->hosts = argv_split(hosts, " ,\t\r\n");
+ if (dict_pgsql->hosts == 0) {
+ argv_add(dict_pgsql->hosts, "localhost", ARGV_END);
+ argv_terminate(dict_pgsql->hosts);
+ if (msg_verbose)
+ msg_info("%s: %s: no hostnames specified, defaulting to '%s'",
+ myname, pgsqlcf, dict_pgsql->hosts->argv[0]);
+ }
+ myfree(hosts);
+}
+
+/* dict_pgsql_open - open PGSQL data base */
+
+DICT *dict_pgsql_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_PGSQL *dict_pgsql;
+
if (open_flags != O_RDONLY)
msg_fatal("%s:%s map requires O_RDONLY access mode",
DICT_TYPE_PGSQL, name);
sizeof(DICT_PGSQL));
dict_pgsql->dict.lookup = dict_pgsql_lookup;
dict_pgsql->dict.close = dict_pgsql_close;
- dict_pgsql->name = pgsqlname_parse(name);
- dict_pgsql->pldb = plpgsql_init(dict_pgsql->name->hostnames,
- dict_pgsql->name->len_hosts);
+ pgsql_parse_config(dict_pgsql, name);
+ dict_pgsql->pldb = plpgsql_init(dict_pgsql->hosts);
dict_pgsql->dict.flags = dict_flags | DICT_FLAG_FIXED;
if (dict_pgsql->pldb == NULL)
msg_fatal("couldn't intialize pldb!\n");
return &dict_pgsql->dict;
}
-/* pgsqlname_parse - parse pgsql configuration file */
-static PGSQL_NAME *pgsqlname_parse(const char *pgsqlcf)
-{
- const char *myname = "pgsqlname_parse";
- int i;
- char *hosts;
- PGSQL_NAME *name = (PGSQL_NAME *) mymalloc(sizeof(PGSQL_NAME));
- ARGV *hosts_argv;
-
- name->parser = cfg_parser_alloc(pgsqlcf);
-
- /* username */
- name->username = cfg_get_str(name->parser, "user", "", 0, 0);
-
- /* password */
- name->password = cfg_get_str(name->parser, "password", "", 0, 0);
-
- /* database name lookup */
- name->dbname = cfg_get_str(name->parser, "dbname", "", 1, 0);
-
- /*
- * See what kind of lookup we have - a traditional 'select' or a function
- * call
- */
- name->select_function = cfg_get_str(name->parser, "select_function",
- NULL, 0, 0);
- name->query = cfg_get_str(name->parser, "query", NULL, 0, 0);
-
- if (name->select_function == 0 && name->query == 0) {
-
- /*
- * We have an old style 'select %s from %s...' call
- */
-
- /* table name */
- name->table = cfg_get_str(name->parser, "table", "", 1, 0);
-
- /* select field */
- name->select_field = cfg_get_str(name->parser, "select_field",
- "", 1, 0);
-
- /* where field */
- name->where_field = cfg_get_str(name->parser, "where_field",
- "", 1, 0);
-
- /* additional conditions */
- name->additional_conditions = cfg_get_str(name->parser,
- "additional_conditions",
- "", 0, 0);
- } else {
- name->table = 0;
- name->select_field = 0;
- name->where_field = 0;
- name->additional_conditions = 0;
- }
-
- /* server hosts */
- hosts = cfg_get_str(name->parser, "hosts", "", 0, 0);
-
- /* coo argv interface */
- hosts_argv = argv_split(hosts, " ,\t\r\n");
- if (hosts_argv->argc == 0) { /* no hosts specified,
- * default to 'localhost' */
- if (msg_verbose)
- msg_info("%s: %s: no hostnames specified, defaulting to 'localhost'",
- myname, pgsqlcf);
- argv_add(hosts_argv, "localhost", ARGV_END);
- argv_terminate(hosts_argv);
- }
- name->len_hosts = hosts_argv->argc;
- name->hostnames = (char **) mymalloc((sizeof(char *)) * name->len_hosts);
- i = 0;
- for (i = 0; hosts_argv->argv[i] != NULL; i++) {
- name->hostnames[i] = mystrdup(hosts_argv->argv[i]);
- if (msg_verbose)
- msg_info("%s: %s: adding host '%s' to list of pgsql server hosts",
- myname, pgsqlcf, name->hostnames[i]);
- }
- myfree(hosts);
- argv_free(hosts_argv);
- return name;
-}
-
+/* plpgsql_init - initalize a PGSQL database */
-/*
- * plpgsql_init - initalize a PGSQL database.
- * Return NULL on failure, or a PLPGSQL * on success.
- */
-static PLPGSQL *plpgsql_init(char *hostnames[], int len_hosts)
+static PLPGSQL *plpgsql_init(ARGV *hosts)
{
PLPGSQL *PLDB;
int i;
- if ((PLDB = (PLPGSQL *) mymalloc(sizeof(PLPGSQL))) == NULL) {
- msg_fatal("mymalloc of pldb failed");
- }
- PLDB->len_hosts = len_hosts;
- if ((PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * len_hosts)) == NULL)
- return NULL;
- for (i = 0; i < len_hosts; i++) {
- PLDB->db_hosts[i] = host_init(hostnames[i]);
- }
+ PLDB = (PLPGSQL *) mymalloc(sizeof(PLPGSQL));
+ PLDB->len_hosts = hosts->argc;
+ PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc);
+ for (i = 0; i < hosts->argc; i++)
+ PLDB->db_hosts[i] = host_init(hosts->argv[i]);
+
return PLDB;
}
/* host_init - initialize HOST structure */
+
static HOST *host_init(const char *hostname)
{
const char *myname = "pgsql host_init";
return host;
}
-/**********************************************************************
- * public interface dict_pgsql_close
- * unregister, disassociate from database, freeing appropriate memory
- **********************************************************************/
+/* dict_pgsql_close - close PGSQL data base */
+
static void dict_pgsql_close(DICT *dict)
{
int i;
DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
plpgsql_dealloc(dict_pgsql->pldb);
- cfg_parser_free(dict_pgsql->name->parser);
- myfree(dict_pgsql->name->username);
- myfree(dict_pgsql->name->password);
- myfree(dict_pgsql->name->dbname);
- if (dict_pgsql->name->table)
- myfree(dict_pgsql->name->table);
- if (dict_pgsql->name->query)
- myfree(dict_pgsql->name->query);
- if (dict_pgsql->name->select_function)
- myfree(dict_pgsql->name->select_function);
- if (dict_pgsql->name->select_field)
- myfree(dict_pgsql->name->select_field);
- if (dict_pgsql->name->where_field)
- myfree(dict_pgsql->name->where_field);
- if (dict_pgsql->name->additional_conditions)
- myfree(dict_pgsql->name->additional_conditions);
- for (i = 0; i < dict_pgsql->name->len_hosts; i++) {
- myfree(dict_pgsql->name->hostnames[i]);
- }
- myfree((char *) dict_pgsql->name->hostnames);
- myfree((char *) dict_pgsql->name);
+ cfg_parser_free(dict_pgsql->parser);
+ myfree(dict_pgsql->username);
+ myfree(dict_pgsql->password);
+ myfree(dict_pgsql->dbname);
+ myfree(dict_pgsql->query);
+ myfree(dict_pgsql->result_format);
+ if (dict_pgsql->domain)
+ string_list_free(dict_pgsql->domain);
+ if (dict_pgsql->hosts)
+ argv_free(dict_pgsql->hosts);
+ if (dict_pgsql->ctx)
+ db_common_free_ctx(dict_pgsql->ctx);
dict_free(dict);
}
/* plpgsql_dealloc - free memory associated with PLPGSQL close databases */
+
static void plpgsql_dealloc(PLPGSQL *PLDB)
{
int i;
* Patches change the patchlevel and the release date. Snapshots change the
* release date only.
*/
-#define MAIL_RELEASE_DATE "20050304"
-#define MAIL_VERSION_NUMBER "2.2"
+#define MAIL_RELEASE_DATE "20050308"
+#define MAIL_VERSION_NUMBER "2.3"
#define VAR_MAIL_VERSION "mail_version"
#ifdef SNAPSHOT
if (addr_list == 0) {
if (var_smtp_defer_mxaddr)
smtp_errno = SMTP_ERR_RETRY;
- msg_warn("no MX host for %s has a valid A record", name);
+ msg_warn("no MX host for %s has a valid address record", name);
break;
}
best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
else
domain = name;
+ /*
+ * Treat an address literal as its own MX server, just like we treat a
+ * name without MX record as its own MX server. There is, however, no
+ * applicable NS server equivalent.
+ */
+ if (*domain == '[') {
+ char *saved_addr;
+ const char *bare_addr;
+ int len;
+
+ if (type != T_MX)
+ return (SMTPD_CHECK_DUNNO);
+ len = strlen(domain);
+ if (domain[len - 1] != ']')
+ return (SMTPD_CHECK_DUNNO);
+ /* Memory leak alert: no early returns after this point. */
+ saved_addr = mystrndup(domain + 1, len - 2);
+ if ((bare_addr = valid_mailhost_addr(saved_addr, DONT_GRIPE)) == 0)
+ status = SMTPD_CHECK_DUNNO;
+ else
+ status = check_addr_access(state, table, bare_addr, FULL,
+ &found, reply_name, reply_class,
+ def_acl);
+ myfree(saved_addr);
+ return (status);
+ }
+
/*
* If the domain name does not exist then we apply no restriction.
*