From: Wietse Venema Date: Tue, 8 Mar 2005 05:00:00 +0000 (-0500) Subject: postfix-2.3-20050308 X-Git-Tag: v2.3-RC1~96 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9228cc5a291534e898db117bfebabff6afeec4af;p=thirdparty%2Fpostfix.git postfix-2.3-20050308 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index c84702b75..72120d6b1 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -10341,6 +10341,13 @@ Apologies for any names omitted. 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. @@ -10445,6 +10452,11 @@ Apologies for any names omitted. 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? diff --git a/postfix/README_FILES/ADDRESS_REWRITING_README b/postfix/README_FILES/ADDRESS_REWRITING_README index 745e7dec4..8d4936e91 100644 --- a/postfix/README_FILES/ADDRESS_REWRITING_README +++ b/postfix/README_FILES/ADDRESS_REWRITING_README @@ -129,58 +129,59 @@ this document for the first time, skip forward to "Address rewriting when mail is received". Once you've finished reading the remainder of this document, the table will help you to quickly find what you need. - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - |AAddddrreessss |SSccooppee |DDaaeemmoonn |GGlloobbaall ttuurrnn--oonn |SSeelleeccttiivvee ttuurrnn--ooffff | - |mmaanniippuullaattiioonn| | |ccoonnttrrooll |ccoonnttrrooll | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Rewrite | |trivial-|append_at_myorigin, | | - |addresses to|all mail|rewrite |append_dot_mydomain,|none | - |standard | |(8) |swap_bangpath, | | - |form | | |allow_percent_hack | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Canonical | |cleanup | | | - |address |all mail|(8) |canonical_maps |receive_override_options| - |mapping | | | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Address |all mail|cleanup |masquerade_domains |receive_override_options| - |masquerading| |(8) | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Automatic | |cleanup |always_bcc, | | - |BCC |new mail|(8) |sender_bcc_maps, |receive_override_options| - |recipients | | |recipient_bcc_maps | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Virtual |all mail|cleanup |virtual_alias_maps |receive_override_options| - |aliasing | |(8) | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Resolve | |trivial-| | | - |address to |all mail|rewrite |none |none | - |destination | |(8) | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Mail | |trivial-| | | - |transport |all mail|rewrite |transport_maps |none | - |switch | |(8) | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Relocated | |trivial-| | | - |users table |all mail|rewrite |relocated_maps |none | - | | |(8) | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Generic |outgoing| | | | - |mapping |SMTP |smtp(8) |smtp_generic_maps |none | - |table |mail | | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Local alias |local | | | | - |database |mail |local(8)|alias_maps |none | - | |only | | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Local per- |local | | | | - |user |mail |local(8)|forward_path |none | - |.forward |only | | | | - |files | | | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | - |Local catch-|local | | | | - |all address |mail |local(8)|luser_relay |none | - | |only | | | | - |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + |AAddddrreessss |SSccooppee |DDaaeemmoonn |GGlloobbaall ttuurrnn--oonn |SSeelleeccttiivvee ttuurrnn--ooffff ccoonnttrrooll | + |mmaanniippuullaattiioonn| | |ccoonnttrrooll | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |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 | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Canonical | |cleanup | |receive_override_options, | + |address |all mail|(8) |canonical_maps |local_header_rewrite_clients,| + |mapping | | | |remote_header_rewrite_domain | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Address | |cleanup | |receive_override_options, | + |masquerading|all mail|(8) |masquerade_domains |local_header_rewrite_clients,| + | | | | |remote_header_rewrite_domain | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Automatic | |cleanup |always_bcc, | | + |BCC |new mail|(8) |sender_bcc_maps, |receive_override_options | + |recipients | | |recipient_bcc_maps | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Virtual |all mail|cleanup |virtual_alias_maps |receive_override_options | + |aliasing | |(8) | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Resolve | |trivial-| | | + |address to |all mail|rewrite |none |none | + |destination | |(8) | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Mail | |trivial-| | | + |transport |all mail|rewrite |transport_maps |none | + |switch | |(8) | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Relocated | |trivial-| | | + |users table |all mail|rewrite |relocated_maps |none | + | | |(8) | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Generic |outgoing| | | | + |mapping |SMTP |smtp(8) |smtp_generic_maps |none | + |table |mail | | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Local alias |local | | | | + |database |mail |local(8)|alias_maps |none | + | |only | | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Local per- |local | | | | + |user |mail |local(8)|forward_path |none | + |.forward |only | | | | + |files | | | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Local catch-|local | | | | + |all address |mail |local(8)|luser_relay |none | + | |only | | | | + |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | AAddddrreessss rreewwrriittiinngg wwhheenn mmaaiill iiss rreecceeiivveedd @@ -213,11 +214,11 @@ table, it first rewrites the address to the standard 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: @@ -246,10 +247,13 @@ 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, @@ -262,9 +266,13 @@ address manipulations: (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 @@ -281,11 +289,11 @@ message envelopes and in message headers. By default all header and envelope 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 @@ -345,11 +353,11 @@ Address masquerading is a method to hide hosts inside a domain behind their 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 diff --git a/postfix/README_FILES/LDAP_README b/postfix/README_FILES/LDAP_README index 39dc85c91..12f31d110 100644 --- a/postfix/README_FILES/LDAP_README +++ b/postfix/README_FILES/LDAP_README @@ -180,7 +180,7 @@ NNootteess aanndd tthhiinnggss ttoo tthhiinnkk aabboouu * 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 qquueerryy__ffiilltteerr should probably look something like this: query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*") @@ -196,7 +196,7 @@ NNootteess aanndd tthhiinnggss ttoo tthhiinnkk aabboouu 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 + qquueerryy__ffiilltteerr, and keep things like majordomo lists in local alias databases. query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*") @@ -236,12 +236,17 @@ CCrreeddiittss 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. diff --git a/postfix/README_FILES/MYSQL_README b/postfix/README_FILES/MYSQL_README index d5b7e27c5..6ebbc234e 100644 --- a/postfix/README_FILES/MYSQL_README +++ b/postfix/README_FILES/MYSQL_README @@ -62,22 +62,16 @@ password = some_password # 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' AAddddiittiioonnaall nnootteess @@ -100,4 +94,7 @@ CCrreeddiittss 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. diff --git a/postfix/README_FILES/PGSQL_README b/postfix/README_FILES/PGSQL_README index 439781022..c7d8c279f 100644 --- a/postfix/README_FILES/PGSQL_README +++ b/postfix/README_FILES/PGSQL_README @@ -63,25 +63,15 @@ password = some_password # 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' UUssiinngg mmiirrrroorreedd ddaattaabbaasseess @@ -107,4 +97,9 @@ CCrreeddiittss * 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. diff --git a/postfix/RELEASE_NOTES-2.2 b/postfix/RELEASE_NOTES-2.2 index a973f43e0..45ac18ed5 100644 --- a/postfix/RELEASE_NOTES-2.2 +++ b/postfix/RELEASE_NOTES-2.2 @@ -20,13 +20,17 @@ the following sections of this document. - 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 @@ -273,6 +277,12 @@ it does not turn off the actual features in the SMTP server. 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. diff --git a/postfix/auxiliary/postfinger/postfinger b/postfix/auxiliary/postfinger/postfinger deleted file mode 100755 index ec43099e9..000000000 --- a/postfix/auxiliary/postfinger/postfinger +++ /dev/null @@ -1,242 +0,0 @@ -#!/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 -# Victor Duchovni -# Sasa Babic -# IƱaki Arenaza -# Jorge Gordoy -# $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 ". - -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 </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 --" diff --git a/postfix/conf/post-install b/postfix/conf/post-install index 95775d064..1119bcec6 100644 --- a/postfix/conf/post-install +++ b/postfix/conf/post-install @@ -594,7 +594,7 @@ EOF 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 diff --git a/postfix/html/ADDRESS_REWRITING_README.html b/postfix/html/ADDRESS_REWRITING_README.html index 8079a7357..9a7939aa9 100644 --- a/postfix/html/ADDRESS_REWRITING_README.html +++ b/postfix/html/ADDRESS_REWRITING_README.html @@ -317,15 +317,18 @@ turn-off control Rewrite addresses to standard form all mail trivial-
rewrite(8)
append_at_myorigin, append_dot_mydomain, swap_bangpath, -allow_percent_hack none +allow_percent_hack local_header_rewrite_clients, +remote_header_rewrite_domain - Canonical address mapping - all mail cleanup(8) -canonical_maps receive_override_options + Canonical address mapping + all mail cleanup(8) canonical_maps + receive_override_options, local_header_rewrite_clients, +remote_header_rewrite_domain - Address masquerading - all mail cleanup(8) masquerade_domains - receive_override_options + Address masquerading all mail cleanup(8) masquerade_domains + receive_override_options, local_header_rewrite_clients, +remote_header_rewrite_domain Automatic BCC recipients new mail cleanup(8) always_bcc, @@ -411,11 +414,12 @@ mapping lookup table, it first rewrites the address to the 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:

@@ -455,10 +459,13 @@ parameter (default: yes). You should never turn off this feature, because a lot 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 @@ -476,10 +483,13 @@ Rewrite "user@host" to "user@host.$mydomain -

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 @@ -502,11 +512,12 @@ addresses in message envelopes and in message headers. By default all header and envelope 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 @@ -585,11 +596,12 @@ behind their 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 diff --git a/postfix/html/LDAP_README.html b/postfix/html/LDAP_README.html index 4e7e5184c..e828b2848 100644 --- a/postfix/html/LDAP_README.html +++ b/postfix/html/LDAP_README.html @@ -257,7 +257,7 @@ maildrop: this, that, theother 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:

+ delivery. Your query_filter should probably look something like this:

@@ -276,7 +276,7 @@ query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*
   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 query_filter, and keep things like majordomo lists in local alias
   databases. 

@@ -334,13 +334,20 @@ contents, please include the applicable bits of some directory entries.

  • 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.
  • 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. diff --git a/postfix/html/MYSQL_README.html b/postfix/html/MYSQL_README.html index e014783df..f243075c2 100644 --- a/postfix/html/MYSQL_README.html +++ b/postfix/html/MYSQL_README.html @@ -89,22 +89,16 @@ password = some_password # 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'

    Additional notes

    @@ -129,10 +123,14 @@ will be deferred until at least one of those hosts is reachable.
    • The initial version was contributed by Scott Cotton and Joshua -Marcus, IC Group, Inc. +Marcus, IC Group, Inc.
    • + +
    • 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.
    diff --git a/postfix/html/PGSQL_README.html b/postfix/html/PGSQL_README.html index 35bb67491..254f580dd 100644 --- a/postfix/html/PGSQL_README.html +++ b/postfix/html/PGSQL_README.html @@ -88,25 +88,15 @@ password = some_password # 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'

    Using mirrored databases

    @@ -130,17 +120,24 @@ those hosts is reachable.

    • This code is based upon the Postfix mysql map by Scott Cotton -and Joshua Marcus, IC Group, Inc. +and Joshua Marcus, IC Group, Inc.
    • -
    • The PostgreSQL changes were done by Aaron Sethman. +
    • The PostgreSQL changes were done by Aaron Sethman.
    • 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.
    • -
    • LaMont Jones was the initial Postfix pgsql maintainer. +
    • LaMont Jones was the initial Postfix pgsql maintainer.
    • Liviu Daia revised the configuration interface and added the -main.cf configuration feature. +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.
    diff --git a/postfix/html/ldap_table.5.html b/postfix/html/ldap_table.5.html index 7c1c51e84..c989d3089 100644 --- a/postfix/html/ldap_table.5.html +++ b/postfix/html/ldap_table.5.html @@ -54,6 +54,20 @@ LDAP_TABLE(5) LDAP_TABLE(5) 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 result_fil- + ter (a name that has caused some confusion as to its mean- + ing in the past) has been renamed to result_format. For + backwards compatibility with the pre 2.2 LDAP client, + result_filter can for now be used instead of result_for- + mat, 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 When using LDAP to store lists such as $mynetworks, $mydestination, $relay_domains, $local_recipient_maps, @@ -114,15 +128,57 @@ LDAP_TABLE(5) LDAP_TABLE(5) The port the LDAP server listens on, e.g. server_port = 778 + timeout (default: 10 seconds) + The number of seconds a search can take before tim- + ing out, e.g. + timeout = 5 + search_base (No default; you must configure this) The RFC2253 base DN at which to conduct the search, e.g. search_base = dc=your, dc=com - timeout (default: 10 seconds) - 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: + + %% This is replaced by a literal '%' character. + + %s This is replaced by the input key. RFC 2253 + quoting is used to make sure that the input + key does not add unexpected metacharacters. + + %u When the input key is an address of the form + user@domain, %u is replaced by the (RFC + 2253) quoted local part of the address. + Otherwise, %u is replaced by the entire + search string. If the localpart is empty, + the search is suppressed and returns no + results. + + %d When the input key is an address of the form + user@domain, %d is replaced by the (RFC + 2253) quoted domain part of the address. + Otherwise, the search is suppressed and + returns no results. + + %[SUD] For the search_base parameter, the upper- + case equivalents of the above expansions + behave identically to their lower-case + counter-parts. With the result_format param- + eter (previously called result_filter see + the COMPATIBILITY section and below), they + expand to the corresponding components of + input key rather than the result value. + + %[1-9] The patterns %1, %2, ... %9 are replaced by + the corresponding most significant component + of the input key's domain. If the input key + is user@mail.example.com, then %1 is com, %2 + is example and %3 is mail. 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. query_filter (default: mailacceptinggeneralid=%s) The RFC2254 filter used to search the directory, @@ -133,21 +189,51 @@ LDAP_TABLE(5) LDAP_TABLE(5) This parameter supports the following '%' expan- sions: - %s 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 a literal '%' character. + (Postfix 2.2 and later). + + %s This is replaced by the input key. RFC 2254 + quoting is used to make sure that the input + key does not add unexpected metacharacters. %u When the input key is an address of the form - user@domain, %u is replaced by the (RFC - 2254) quoted local part of the address. Oth- - erwise, %u is replaced by the entire search - string. + user@domain, %u is replaced by the (RFC + 2254) quoted local part of the address. + Otherwise, %u is replaced by the entire + search string. If the localpart is empty, + the search is suppressed and returns no + results. %d When the input key is an address of the form - user@domain, %d is replaced by the (RFC - 2254) quoted domain part of the address. - Otherwise, %d is replaced by the entire - search string. + user@domain, %d is replaced by the (RFC + 2254) quoted domain part of the address. + Otherwise, the search is suppressed and + returns no results. + + %[SUD] The upper-case equivalents of the above + expansions behave in the query_filter param- + eter identically to their lower-case + counter-parts. With the result_format param- + eter (previously called result_filter 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. + + %[1-9] The patterns %1, %2, ... %9 are replaced by + the corresponding most significant component + of the input key's domain. If the input key + is user@mail.example.com, then %1 is com, %2 + is example and %3 is mail. 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 @@ -156,34 +242,53 @@ LDAP_TABLE(5) LDAP_TABLE(5) matching domains are suppressed and return no results. - NOTE: DO NOT put quotes around the query filter. + NOTE: DO NOT put quotes around the query_filter + parameter. - result_filter (default: %s) - 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: + result_format (default: %s) + Called result_filter 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: + + %% This is replaced by a literal '%' character. + (Postfix 2.2 and later). %s This is replaced by the value of the result - attribute. + attribute. When result is empty it is + skipped. - %u When the result attribute value is an + %u When the result attribute value is an address of the form user@domain, %u is - replaced by the local part of the address. - Otherwise, %u is replaced by the entire - attribute value. - - %d When a result attribute value is an address - of the form user@domain, %d is replaced by - the domain part of the attribute value. - Otherwise, %d is replaced by the entire - attribute value. - - For example, using "result_filter = smtp:[%s]" + replaced by the local part of the address. + When the result has an empty localpart it is + skipped. + + %d When a result attribute value is an address + of the form user@domain, %d is replaced by + the domain part of the attribute value. When + the result is unqualified it is skipped. + + %[SUD1-9] + 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 query_fil- + ter, 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 = smtp:[%s]" allows one to use a mailHost attribute as the basis of a transport(5) 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 @@ -193,77 +298,84 @@ LDAP_TABLE(5) LDAP_TABLE(5) The default value %s specifies that each attribute value should be used as is. - NOTE: DO NOT put quotes around the result filter! + This parameter was called result_filter 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! domain (default: no domain list) - 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 local(8) + NOTE: DO NOT define this parameter for local(8) aliases. result_attribute (default: maildrop) - 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 special_result_attribute (No default) 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. scope (default: sub) - The LDAP search scope: sub, base, or one. These + The LDAP search scope: sub, base, or one. These translate into LDAP_SCOPE_SUBTREE, LDAP_SCOPE_BASE, and LDAP_SCOPE_ONELEVEL. bind (default: yes) - 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. bind_dn (default: empty) - 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 bind_pw (default: empty) - 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 ldap:ldapsource syn- + user. When using the obsolete ldap: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: @@ -274,43 +386,43 @@ LDAP_TABLE(5) LDAP_TABLE(5) cache_expiry (IGNORED with a warning) cache_size (IGNORED with a warning) - 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. recursion_limit (default: 1000) - 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. expansion_limit (default: 0) - 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. size_limit (default: $expansion_limit) - 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". dereference (default: 0) - 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 @@ -322,99 +434,99 @@ LDAP_TABLE(5) LDAP_TABLE(5) 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. chase_referrals (default: 0) - Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP + Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP version 3 support). version (default: 2) Specifies the LDAP protocol version to use. debuglevel (default: 0) - What level to set for debugging in the OpenLDAP + What level to set for debugging in the OpenLDAP libraries. LDAP SSL AND STARTTLS PARAMETERS - 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: start_tls (default: no) 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). - tls_ca_cert_dir (No default; set either this or + tls_ca_cert_dir (No default; set either this or tls_ca_cert_file) 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. - tls_ca_cert_file (No default; set either this or + tls_ca_cert_file (No default; set either this or tls_ca_cert_dir) 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. tls_cert (No default; you must set this) - File containing client's X509 certificate to be + File containing client's X509 certificate to be used by the client in SSL/ TLS connections. tls_key (No default; you must set this) - File containing the private key corresponding to + File containing the private key corresponding to the above tls_cert. tls_require_cert (default: no) 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. tls_random_file (No default) - 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. tls_cipher_suite (No default) Cipher suite to use in SSL/TLS negotiations. EXAMPLE - Here's a basic example for using LDAP to look up local(8) + Here's a basic example for using LDAP to look up local(8) aliases. Assume that in main.cf, you have: alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf @@ -423,14 +535,14 @@ LDAP_TABLE(5) LDAP_TABLE(5) 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 RFC822 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 RFC822 addresses to which the message will be delivered. SEE ALSO @@ -444,13 +556,13 @@ LDAP_TABLE(5) LDAP_TABLE(5) LDAP_README, Postfix LDAP client guide LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. AUTHOR(S) - 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) diff --git a/postfix/html/mysql_table.5.html b/postfix/html/mysql_table.5.html index 74c208892..fc75c4d2c 100644 --- a/postfix/html/mysql_table.5.html +++ b/postfix/html/mysql_table.5.html @@ -28,7 +28,7 @@ MYSQL_TABLE(5) MYSQL_TABLE(5) as the Postfix main.cf file, and can specify the parame- ters described below. -ALTERNATIVE CONFIGURATION +BACKWARDS COMPATIBILITY 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 @@ -44,6 +44,27 @@ MYSQL_TABLE(5) MYSQL_TABLE(5) 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 query parameter + (described in more detail below). When the new query + parameter is not specified in the map definition, Postfix + reverts to the old interface, with the SQL query con- + structed from the select_field, table, where_field and + additional_conditions parameters. The old interface will + be gradually phased out. To migrate to the new interface + set: + + query = SELECT [select_field] + FROM [table] + WHERE [where_field] = '%s' + [additional_conditions] + + Insert the value, not the name, of each legacy parameter. + Note that the additional_conditions parameter is optional + and if not empty, will always start with AND. + LIST MEMBERSHIP When using SQL to store lists such as $mynetworks, $mydes- tination, $relay_domains, $local_recipient_maps, etc., it @@ -91,29 +112,189 @@ MYSQL_TABLE(5) MYSQL_TABLE(5) dbname 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 [select_field] from [table] where - [where_field] = '$lookup' [additional_conditions] + query The SQL query template used to search the database, + where %s 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: + + %% This is replaced by a literal '%' character. + + %s This is replaced by the input key. SQL + quoting is used to make sure that the input + key does not add unexpected metacharacters. + + %u When the input key is an address of the form + user@domain, %u is replaced by the SQL + quoted local part of the address. Other- + wise, %u is replaced by the entire search + string. If the localpart is empty, the + query is suppressed and returns no results. + + %d When the input key is an address of the form + user@domain, %d is replaced by the SQL + quoted domain part of the address. Other- + wise, the query is suppressed and returns no + results. + + %[SUD] The upper-case equivalents of the above + expansions behave in the query parameter + identically to their lower-case counter- + parts. With the result_format parameter + (see below), they expand the input key + rather than the result value. + + %[1-9] The patterns %1, %2, ... %9 are replaced by + the corresponding most significant component + of the input key's domain. If the input key + is user@mail.example.com, then %1 is com, %2 + is example and %3 is mail. 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 domain parameter described below limits the + input keys to addresses in matching domains. When + the domain 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: select_field, table, + where_field and additional_conditions. The mapping + from the old parameters to the equivalent query is: + + SELECT [select_field] + FROM [table] + WHERE [where_field] = '%s' + [additional_conditions] + + The '%s' in the WHERE clause expands to the escaped + search string. With Postfix 2.2 these legacy + parameters are used if the query parameter is not + specified. + + NOTE: DO NOT put quotes around the query parameter. + + result_format (default: %s) + Format template applied to result attributes. Most + commonly used to append (or prepend) text to the + result. This parameter supports the following '%' + expansions: + + %% This is replaced by a literal '%' character. + + %s This is replaced by the value of the result + attribute. When result is empty it is + skipped. + + %u When the result attribute value is an + address of the form user@domain, %u is + replaced by the local part of the address. + When the result has an empty localpart it is + skipped. + + %d When a result attribute value is an address + of the form user@domain, %d is replaced by + the domain part of the attribute value. When + the result is unqualified it is skipped. + + %[SUD1-9] + 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 query, 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 = 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 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 %s 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! + + domain (default: no domain list) + 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 local(8) + aliases, because the input keys are always unquali- + fied. + + expansion_limit (default: 0) + 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 [select_field] + FROM [table] + WHERE [where_field] = '%s' + [additional_conditions] + + 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 query interface described + above. If the query 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. select_field The SQL "select" parameter. Example: - select_field = forw_addr + select_field = forw_addr table The SQL "select .. from" table name. Example: - table = mxaliases + table = mxaliases where_field The SQL "select .. where" parameter. Example: - where_field = alias + where_field = alias additional_conditions Additional conditions to the SQL query. Example: - additional_conditions = and status = 'paid' + additional_conditions = AND status = 'paid' SEE ALSO postmap(1), Postfix lookup table maintenance @@ -126,7 +307,7 @@ MYSQL_TABLE(5) MYSQL_TABLE(5) MYSQL_README, Postfix MYSQL client guide LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. HISTORY diff --git a/postfix/html/pgsql_table.5.html b/postfix/html/pgsql_table.5.html index fbf1009ac..28412b428 100644 --- a/postfix/html/pgsql_table.5.html +++ b/postfix/html/pgsql_table.5.html @@ -29,7 +29,7 @@ PGSQL_TABLE(5) PGSQL_TABLE(5) as the Postfix main.cf file, and can specify the parame- ters described below. -ALTERNATIVE CONFIGURATION +BACKWARDS COMPATIBILITY 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 @@ -45,44 +45,72 @@ PGSQL_TABLE(5) PGSQL_TABLE(5) 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 query parameter + (described in more detail below). In Postfix 2.1 the + parameter precedence was, from highest to lowest, + select_function, query and finally select_field, ... + + With Postfix 2.2 the query parameter has highest prece- + dence, and is used in preference to the still supported, + but slated to be phased out, select_function, + select_field, table, where_field and additional_conditions + parameters. To migrate to the new interface set: + + query = SELECT select_function('%s') + + or in the absense of selection_function, the lower prece- + dence: + + query = SELECT select_field + FROM table + WHERE where_field = '%s' + additional_conditions + + Use the value, not the name, of each legacy parameter. + Note that the additional_conditions parameter is optional + and if not empty, will always start with AND. + LIST MEMBERSHIP When using SQL to store lists such as $mynetworks, $mydes- - tination, $relay_domains, $local_recipient_maps, 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 DATABASE_README document for a discussion. - - Do NOT create tables that return the full list of domains - in $mydestination or $relay_domains etc., or IP addresses + tination, $relay_domains, $local_recipient_maps, 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 DATABASE_README document for a discussion. + + Do NOT create tables that return the full list of domains + in $mydestination or $relay_domains etc., or IP addresses in $mynetworks. 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. PGSQL PARAMETERS - hosts The hosts that Postfix will try to connect to and + hosts The hosts that Postfix will try to connect to and query from. Specify unix: for UNIX-domain sockets, inet: 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 unix: and inet: 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. user, password - 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 @@ -90,58 +118,155 @@ PGSQL_TABLE(5) PGSQL_TABLE(5) dbname 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 [select_field] from [table] where - [where_field] = '$lookup' [additional_conditions] - - $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. - - select_field - The SQL "select" parameter. Example: - select_field = forw_addr - - table The SQL "select .. from" table name. Example: - table = mxaliases - - where_field - The SQL "select .. where" parameter. Example: - where_field = alias - - additional_conditions - 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 table, select_field, - where_field and additional_conditions parameters: + query The SQL query template used to search the database, + where %s is a substitute for the address Postfix is + trying to resolve, e.g. + query = SELECT replacement FROM aliases WHERE + mailbox = '%s' - query 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: - %s 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 a literal '%' character. + (Postfix 2.2 and later) + + %s This is replaced by the input key. SQL + quoting is used to make sure that the input + key does not add unexpected metacharacters. %u When the input key is an address of the form - user@domain, %u is replaced by the quoted - local part of the address. If no domain is - specified, %u is replaced by the entire - search string. + user@domain, %u is replaced by the SQL + quoted local part of the address. Other- + wise, %u is replaced by the entire search + string. If the localpart is empty, the + query is suppressed and returns no results. %d When the input key is an address of the form - user@domain, %d is replaced by the quoted - domain part of the address. When the input - key has no domain qualifier, %d is replaced - by the entire search string. + user@domain, %d is replaced by the SQL + quoted domain part of the address. Other- + wise, the query is suppressed and returns no + results. + + %[SUD] The upper-case equivalents of the above + expansions behave in the query parameter + identically to their lower-case counter- + parts. With the result_format 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 + + %[1-9] The patterns %1, %2, ... %9 are replaced by + the corresponding most significant component + of the input key's domain. If the input key + is user@mail.example.com, then %1 is com, %2 + is example and %3 is mail. 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 domain parameter described below limits the + input keys to addresses in matching domains. When + the domain 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, select_function, query, + select_field, ... + + With Postfix 2.2 the query parameter has highest + precedence, see COMPATIBILITY above. + + NOTE: DO NOT put quotes around the query parameter. + + result_format (default: %s) + Format template applied to result attributes. Most + commonly used to append (or prepend) text to the + result. This parameter supports the following '%' + expansions: + + %% This is replaced by a literal '%' character. + + %s This is replaced by the value of the result + attribute. When result is empty it is + skipped. + + %u When the result attribute value is an + address of the form user@domain, %u is + replaced by the local part of the address. + When the result has an empty localpart it is + skipped. + + %d When a result attribute value is an address + of the form user@domain, %d is replaced by + the domain part of the attribute value. When + the result is unqualified it is skipped. + + %[SUD1-9] + 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 query, 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 = 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 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 %s 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! + + domain (default: no domain list) + 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 local(8) + aliases, because the input keys are always unquali- + fied. + + expansion_limit (default: 0) + 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: select_function This parameter specifies a database function name. @@ -149,18 +274,51 @@ PGSQL_TABLE(5) PGSQL_TABLE(5) 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 query parame- + ter. Starting with Postfix 2.2, the query parameter + has highest precedence, and this parameter is dep- + recated. Please migrate to the new query interface + as this interface is slated to be phased out. + + The following parameters (with lower precedence than the + select_function interface described above) can be used to + build the SQL select statement as follows: + + SELECT [select_field] + FROM [table] + WHERE [where_field] = '%s' + [additional_conditions] + + 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 query interface described above. If + higher precedence the query or select_function parameters + described above are defined, these parameters are ignored. + Please migrate to the new query interface as this inter- + face is slated to be phased out. - and overrides both the query parameter and the ta- - ble-related fields above. + select_field + The SQL "select" parameter. Example: + select_field = 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. + table The SQL "select .. from" table name. Example: + table = mxaliases - Future versions will allow functions to return - result sets. + where_field + The SQL "select .. where" parameter. Example: + where_field = alias + + additional_conditions + Additional conditions to the SQL query. Example: + additional_conditions = AND status = 'paid' SEE ALSO postmap(1), Postfix lookup table manager @@ -173,7 +331,7 @@ PGSQL_TABLE(5) PGSQL_TABLE(5) PGSQL_README, Postfix PostgreSQL client guide LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. HISTORY diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index c712a8068..db9d2883e 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -559,6 +559,24 @@ Enable the rewriting of the form "user%domain" to "user@domain". This is enabled by default.

    +

    Note: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + + + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    +

    Example:

    @@ -689,10 +707,28 @@ append the string "@$remo

    -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.

    +

    Note 2: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + +
    + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    + @@ -707,11 +743,29 @@ instead.

    -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.

    +

    Note 2: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + + + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    + @@ -1105,6 +1159,24 @@ will become visible after a minute or so. Use "postfix reload" to eliminate the delay.

    +

    Note: with Postfix version 2.2, message header address mapping +happens only when message header address rewriting is enabled:

    + + + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    +

    Examples:

    @@ -2659,7 +2731,8 @@ Examples:

    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.

    This feature is available in Postfix version 2.2 and later.

    @@ -3881,6 +3954,24 @@ 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".

    +

    Note: with Postfix version 2.2, message header address masquerading +happens only when message header address rewriting is enabled:

    + + + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    +

    Example:

    @@ -9044,6 +9135,24 @@ necessary if your machine is connected to UUCP networks. It is enabled by default.

    +

    Note: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + + + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    +

    Example:

    diff --git a/postfix/man/man5/ldap_table.5 b/postfix/man/man5/ldap_table.5 index 594f4c4e6..c4b1d8f8a 100644 --- a/postfix/man/man5/ldap_table.5 +++ b/postfix/man/man5/ldap_table.5 @@ -53,6 +53,18 @@ parameter below would be defined in main.cf as 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 @@ -128,14 +140,50 @@ server_host = ldapi://%2Fsome%2Fpath 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, @@ -145,20 +193,43 @@ query_filter = (&(mail=%s)(paid_up=true)) 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 @@ -167,30 +238,42 @@ parameter is non-empty, LDAP queries for unqualified 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 @@ -200,7 +283,13 @@ value. The default value \fB%s\fR specifies that each attribute value should be used as is. -NOTE: DO NOT put quotes around the result 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 @@ -220,7 +309,7 @@ The attribute(s) Postfix will read from any directory 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 @@ -287,14 +376,14 @@ limit to 1 ensures that lookups do not return multiple 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". diff --git a/postfix/man/man5/mysql_table.5 b/postfix/man/man5/mysql_table.5 index 9654a36ce..4ccb097ba 100644 --- a/postfix/man/man5/mysql_table.5 +++ b/postfix/man/man5/mysql_table.5 @@ -27,7 +27,7 @@ alias_maps = mysql:/etc/mysql-aliases.cf 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 @@ -44,6 +44,30 @@ below would be defined in main.cf as "\fImysqlname\fR_hosts". 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 @@ -102,33 +126,176 @@ password = some_password 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 diff --git a/postfix/man/man5/pgsql_table.5 b/postfix/man/man5/pgsql_table.5 index 2428e84ee..6a3925c47 100644 --- a/postfix/man/man5/pgsql_table.5 +++ b/postfix/man/man5/pgsql_table.5 @@ -27,7 +27,7 @@ alias_maps = pgsql:/etc/pgsql-aliases.cf 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 @@ -46,6 +46,37 @@ Note: with this form, the passwords for the PostgreSQL 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 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 @@ -102,61 +133,134 @@ password = some_password 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 @@ -164,16 +268,54 @@ select_function = my_lookup_user_alias 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 diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 4cf5ee78a..3e60aa7fc 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -306,6 +306,20 @@ difficult to enforce consistently and globally. 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 @@ -371,17 +385,45 @@ With locally submitted mail, append the string "@$myorigin" to mail 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. @@ -590,6 +632,20 @@ build the necessary DBM or DB file after every change. The changes 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 @@ -1382,7 +1438,8 @@ inet_interfaces = 192.168.1.2, 127.0.0.1 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 @@ -2077,6 +2134,20 @@ or its subdomains. Thus, 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 @@ -5205,6 +5276,20 @@ Enable the rewriting of "site!user" into "user@site". This is 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 diff --git a/postfix/proto/ADDRESS_REWRITING_README.html b/postfix/proto/ADDRESS_REWRITING_README.html index b681db624..a8ac3d1bd 100644 --- a/postfix/proto/ADDRESS_REWRITING_README.html +++ b/postfix/proto/ADDRESS_REWRITING_README.html @@ -317,15 +317,18 @@ turn-off control Rewrite addresses to standard form all mail trivial-
    rewrite(8) append_at_myorigin, append_dot_mydomain, swap_bangpath, -allow_percent_hack none +allow_percent_hack local_header_rewrite_clients, +remote_header_rewrite_domain - Canonical address mapping - all mail cleanup(8) -canonical_maps receive_override_options + Canonical address mapping + all mail cleanup(8) canonical_maps + receive_override_options, local_header_rewrite_clients, +remote_header_rewrite_domain - Address masquerading - all mail cleanup(8) masquerade_domains - receive_override_options + Address masquerading all mail cleanup(8) masquerade_domains + receive_override_options, local_header_rewrite_clients, +remote_header_rewrite_domain Automatic BCC recipients new mail cleanup(8) always_bcc, @@ -411,11 +414,12 @@ trivial-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:

    @@ -455,10 +459,13 @@ parameter (default: yes). You should never turn off this feature, because a lot 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 @@ -476,10 +483,13 @@ Rewrite "user@host" to "user@host.$mydomain" parameter (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 @@ -502,11 +512,12 @@ addresses in message envelopes and in message headers. By default all header and envelope 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 @@ -585,11 +596,12 @@ behind their 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 diff --git a/postfix/proto/LDAP_README.html b/postfix/proto/LDAP_README.html index 740989067..f9573a897 100644 --- a/postfix/proto/LDAP_README.html +++ b/postfix/proto/LDAP_README.html @@ -257,7 +257,7 @@ maildrop: this, that, theother 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:

    + delivery. Your query_filter should probably look something like this:

    @@ -276,7 +276,7 @@ query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*
       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 query_filter, and keep things like majordomo lists in local alias
       databases. 

    @@ -334,13 +334,20 @@ contents, please include the applicable bits of some directory entries.

  • 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.
  • 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. diff --git a/postfix/proto/MYSQL_README.html b/postfix/proto/MYSQL_README.html index 0e3d3961d..9672f5a9d 100644 --- a/postfix/proto/MYSQL_README.html +++ b/postfix/proto/MYSQL_README.html @@ -89,22 +89,16 @@ password = some_password # 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'

    Additional notes

    @@ -129,10 +123,14 @@ will be deferred until at least one of those hosts is reachable.
    • The initial version was contributed by Scott Cotton and Joshua -Marcus, IC Group, Inc. +Marcus, IC Group, Inc.
    • + +
    • 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.
    diff --git a/postfix/proto/PGSQL_README.html b/postfix/proto/PGSQL_README.html index a819394dd..167c45b95 100644 --- a/postfix/proto/PGSQL_README.html +++ b/postfix/proto/PGSQL_README.html @@ -88,25 +88,15 @@ password = some_password # 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'

    Using mirrored databases

    @@ -130,17 +120,24 @@ those hosts is reachable.

    • This code is based upon the Postfix mysql map by Scott Cotton -and Joshua Marcus, IC Group, Inc. +and Joshua Marcus, IC Group, Inc.
    • -
    • The PostgreSQL changes were done by Aaron Sethman. +
    • The PostgreSQL changes were done by Aaron Sethman.
    • 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.
    • -
    • LaMont Jones was the initial Postfix pgsql maintainer. +
    • LaMont Jones was the initial Postfix pgsql maintainer.
    • Liviu Daia revised the configuration interface and added the -main.cf configuration feature. +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.
    diff --git a/postfix/proto/ldap_table b/postfix/proto/ldap_table index 3c840c309..c3a0fdf13 100644 --- a/postfix/proto/ldap_table +++ b/postfix/proto/ldap_table @@ -45,6 +45,18 @@ # 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 @@ -116,14 +128,50 @@ # 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, @@ -133,20 +181,43 @@ # # 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 @@ -155,30 +226,42 @@ # 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 @@ -188,7 +271,13 @@ # 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 @@ -208,7 +297,7 @@ # 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 @@ -275,14 +364,14 @@ # 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". diff --git a/postfix/proto/mysql_table b/postfix/proto/mysql_table index c7ae4764b..f3b3837ad 100644 --- a/postfix/proto/mysql_table +++ b/postfix/proto/mysql_table @@ -21,7 +21,7 @@ # 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 @@ -36,6 +36,30 @@ # 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 @@ -90,33 +114,176 @@ # 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 diff --git a/postfix/proto/pgsql_table b/postfix/proto/pgsql_table index 6b9702018..2a3be10c3 100644 --- a/postfix/proto/pgsql_table +++ b/postfix/proto/pgsql_table @@ -21,7 +21,7 @@ # 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 @@ -38,6 +38,37 @@ # 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 @@ -90,61 +121,134 @@ # 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 @@ -152,16 +256,54 @@ # # 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 diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 2dd4587ba..d97a5c478 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -511,6 +511,24 @@ Enable the rewriting of the form "user%domain" to "user@domain". This is enabled by default.

    +

    Note: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + +
      + +
    • The message is received with the Postfix sendmail(1) command, + +
    • The message is received from a network client that matches +$local_header_rewrite_clients, + +
    • The message is received from the network, and the +remote_header_rewrite_domain parameter specifies a non-empty value. + +
    + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    +

    Example:

    @@ -685,6 +703,24 @@ will become visible after a minute or so. Use "postfix reload" to eliminate the delay.

    +

    Note: with Postfix version 2.2, message header address mapping +happens only when message header address rewriting is enabled:

    + +
      + +
    • The message is received with the Postfix sendmail(1) command, + +
    • The message is received from a network client that matches +$local_header_rewrite_clients, + +
    • The message is received from the network, and the +remote_header_rewrite_domain parameter specifies a non-empty value. + +
    + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    +

    Examples:

    @@ -1638,7 +1674,8 @@ inet_interfaces = 192.168.1.2, 127.0.0.1

    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.

    This feature is available in Postfix version 2.2 and later.

    @@ -2385,6 +2422,25 @@ 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".

    +

    Note: with Postfix version 2.2, message header address masquerading +happens only when message header address rewriting is enabled:

    + +
      + +
    • The message is received with the Postfix sendmail(1) command, + +
    • The message is received from a network client that matches +$local_header_rewrite_clients, + +
    • The message is received from the network, and the +remote_header_rewrite_domain parameter specifies a non-empty value. + +
    + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    + +

    Example:

    @@ -5478,6 +5534,24 @@ necessary if your machine is connected to UUCP networks. It is enabled by default.

    +

    Note: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + +
      + +
    • The message is received with the Postfix sendmail(1) command, + +
    • The message is received from a network client that matches +$local_header_rewrite_clients, + +
    • The message is received from the network, and the +remote_header_rewrite_domain parameter specifies a non-empty value. + +
    + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    +

    Example:

    @@ -5834,10 +5908,28 @@ append the string "@$remote_header_rewrite_domain" instead.

    -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.

    +

    Note 2: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + +
      + +
    • The message is received with the Postfix sendmail(1) command, + +
    • The message is received from a network client that matches +$local_header_rewrite_clients, + +
    • The message is received from the network, and the +remote_header_rewrite_domain parameter specifies a non-empty value. + +
    + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    + %PARAM append_dot_mydomain yes

    @@ -5848,11 +5940,29 @@ instead.

    -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.

    +

    Note 2: With Postfix version 2.2, message header address rewriting +happens only when one of the following conditions is true:

    + +
      + +
    • The message is received with the Postfix sendmail(1) command, + +
    • The message is received from a network client that matches +$local_header_rewrite_clients, + +
    • The message is received from the network, and the +remote_header_rewrite_domain parameter specifies a non-empty value. + +
    + +

    To get the behavior before Postfix 2.2, specify +"local_header_rewrite_clients = static:all".

    + %PARAM application_event_drain_time 100s

    diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 0e7ec68f9..1ff165321 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -1,7 +1,7 @@ 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 \ @@ -28,7 +28,7 @@ SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.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 \ @@ -73,7 +73,7 @@ HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \ 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) @@ -520,6 +520,20 @@ clnt_stream.o: mail_proto.h 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 @@ -603,10 +617,59 @@ deliver_request.o: recipient_list.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 diff --git a/postfix/src/global/db_common.c b/postfix/src/global/db_common.c new file mode 100644 index 000000000..c694caa1a --- /dev/null +++ b/postfix/src/global/db_common.c @@ -0,0 +1,448 @@ +/*++ +/* 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 +#include + + /* + * Global library. + */ +#include "cfg_parser.h" + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * 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); +} diff --git a/postfix/src/global/db_common.h b/postfix/src/global/db_common.h new file mode 100644 index 000000000..73c466136 --- /dev/null +++ b/postfix/src/global/db_common.h @@ -0,0 +1,55 @@ +#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 + diff --git a/postfix/src/global/dict_ldap.c b/postfix/src/global/dict_ldap.c index d47d2cc82..6e8a4f752 100644 --- a/postfix/src/global/dict_ldap.c +++ b/postfix/src/global/dict_ldap.c @@ -7,7 +7,7 @@ /* #include /* /* DICT *dict_ldap_open(attribute, dummy, dict_flags) -/* const char *attribute; +/* const char *ldapsource; /* int dummy; /* int dict_flags; /* DESCRIPTION @@ -47,11 +47,13 @@ /* .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. @@ -184,8 +186,6 @@ /* Utility library. */ -#include "match_list.h" -#include "match_ops.h" #include "msg.h" #include "mymalloc.h" #include "vstring.h" @@ -196,6 +196,7 @@ /* Global library. */ #include "cfg_parser.h" +#include "db_common.h" /* Application-specific. */ @@ -212,15 +213,17 @@ typedef struct { */ 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; @@ -229,7 +232,6 @@ typedef struct { int timeout; int dereference; long recursion_limit; - long expansion_limit; long size_limit; int chase_referrals; int debuglevel; @@ -251,6 +253,55 @@ typedef struct { #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) @@ -404,7 +455,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) #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, @@ -497,7 +548,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) 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); } /* @@ -582,7 +633,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) if (msg_verbose) msg_info("%s: Cached connection handle for LDAP source %s", - myname, dict_ldap->ldapsource); + myname, dict_ldap->parser->name); return (0); } @@ -643,59 +694,6 @@ static void dict_ldap_conn_find(DICT_LDAP *dict_ldap) 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. @@ -704,7 +702,7 @@ static void dict_ldap_expand_filter(char *ldapsource, char *filter, * 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; @@ -738,10 +736,12 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, * 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); @@ -792,22 +792,16 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, 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; @@ -842,7 +836,7 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, } 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: @@ -875,8 +869,8 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, } 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); @@ -897,14 +891,12 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) 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; @@ -916,24 +908,22 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) * 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 @@ -949,7 +939,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) 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); @@ -960,7 +950,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) 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 @@ -973,89 +963,55 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) 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; @@ -1067,20 +1023,21 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) 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 @@ -1097,8 +1054,24 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) 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. */ @@ -1116,6 +1089,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) * And tell the caller to try again later. */ dict_errno = DICT_ERR_RETRY; + break; } /* @@ -1123,10 +1097,6 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) */ 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 @@ -1148,23 +1118,24 @@ static void dict_ldap_close(DICT *dict) 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); @@ -1202,7 +1173,6 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) 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); @@ -1260,20 +1230,26 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) 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) @@ -1286,7 +1262,6 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) dict_ldap->server_host); #endif myfree(server_host); - vstring_free(url_list); /* * Scope handling thanks to Carsten Hoeger of SuSE. @@ -1312,18 +1287,15 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) 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; } @@ -1335,20 +1307,37 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) * 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"); @@ -1357,9 +1346,8 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) 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); /* @@ -1378,44 +1366,32 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) 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. diff --git a/postfix/src/global/dict_mysql.c b/postfix/src/global/dict_mysql.c index f1abc847c..e16d22933 100644 --- a/postfix/src/global/dict_mysql.c +++ b/postfix/src/global/dict_mysql.c @@ -57,14 +57,69 @@ /* 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 '' +/* 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 @@ -132,6 +187,7 @@ /* Global library. */ #include "cfg_parser.h" +#include "db_common.h" /* Application-specific. */ @@ -156,22 +212,18 @@ typedef struct { } 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) @@ -186,7 +238,7 @@ typedef struct { #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 *); @@ -195,89 +247,120 @@ static void plmysql_connect_single(HOST *, char *, char *, char *); 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 */ @@ -461,12 +544,77 @@ static void plmysql_down_host(HOST *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; @@ -483,95 +631,31 @@ DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags) 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; } @@ -619,29 +703,26 @@ static HOST *host_init(const char *hostname) 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); } diff --git a/postfix/src/global/dict_pgsql.c b/postfix/src/global/dict_pgsql.c index 1657d65e7..78ae4b28a 100644 --- a/postfix/src/global/dict_pgsql.c +++ b/postfix/src/global/dict_pgsql.c @@ -58,24 +58,44 @@ /* 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 @@ -151,6 +171,7 @@ /* Global library. */ #include "cfg_parser.h" +#include "db_common.h" /* Application-specific. */ @@ -183,24 +204,19 @@ typedef struct { } 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; @@ -208,7 +224,7 @@ typedef struct { #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 *); @@ -217,213 +233,129 @@ static void plpgsql_connect_single(HOST *, char *, char *, char *); 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 */ @@ -603,19 +535,87 @@ static void plpgsql_down_host(HOST *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); @@ -624,122 +624,33 @@ DICT *dict_pgsql_open(const char *name, int open_flags, int dict_flags) 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"; @@ -773,41 +684,31 @@ static HOST *host_init(const char *hostname) 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; diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 176781291..7a911e3cd 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,8 +20,8 @@ * 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 diff --git a/postfix/src/smtp/smtp_addr.c b/postfix/src/smtp/smtp_addr.c index 5bdc493d7..24f08902a 100644 --- a/postfix/src/smtp/smtp_addr.c +++ b/postfix/src/smtp/smtp_addr.c @@ -416,7 +416,7 @@ DNS_RR *smtp_domain_addr(char *name, int misc_flags, VSTRING *why, 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); diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 786df955e..5e59e2b9c 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -2260,6 +2260,33 @@ static int check_server_access(SMTPD_STATE *state, const char *table, 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. *