From: Wietse Venema
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.
-The Postfix trivial-rewrite(8) daemon implements the following hard-coded address manipulations:
@@ -430,21 +427,41 @@ hard-coded address manipulations: is called a route address, and specifies that mail for "user@site" be delivered via "hosta" and "hostb". Usage of this form has been deprecated for a long time. Postfix has no ability to handle route -addresses, other than to strip off the route part. +addresses, other than to strip off the route part. + +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".
This feature is controlled by the boolean swap_bangpath parameter (default: yes). The purpose is to rewrite UUCP-style addresses to domain style. This is useful only when you receive -mail via UUCP, but it probably does not hurt otherwise.
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".
This feature is controlled by the boolean allow_percent_hack parameter (default: yes). Typically, this is used in order to deal with monstrosities such as "user%domain@otherdomain".
-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".
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 +496,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 @@ -489,7 +512,14 @@ of having Postfix's own domain appended automatically.
A single trailing dot is silently removed. However, an address that ends in multiple dots will be rejected as an invalid -address.
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".
@@ -502,11 +532,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 +616,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.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'
- 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.
Using mirrored databases
@@ -130,17 +120,24 @@ those hosts is reachable.diff --git a/postfix/html/STANDARD_CONFIGURATION_README.html b/postfix/html/STANDARD_CONFIGURATION_README.html index 00411616a..188e023f4 100644 --- a/postfix/html/STANDARD_CONFIGURATION_README.html +++ b/postfix/html/STANDARD_CONFIGURATION_README.html @@ -430,7 +430,7 @@ listening on the internal interface. In such a configuration is it is tempting to configure $inet_interfaces in each instance with just the corresponding interface address. -
- 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.
In most cases using inet_interaces in this way will not work, +
In most cases, using inet_interfaces in this way will not work, because as documented in the $inet_interfaces reference manual, the smtp(8) delivery agent will also use the specified interface address as the source address for outbound connections and will be unable to diff --git a/postfix/html/canonical.5.html b/postfix/html/canonical.5.html index e47e4f273..ef5b9f396 100644 --- a/postfix/html/canonical.5.html +++ b/postfix/html/canonical.5.html @@ -47,6 +47,14 @@ CANONICAL(5) CANONICAL(5) Sendmail rule set S3, if you like. This is controlled with the canonical_classes parameter. + NOTE: Postfix versions 2.2 and later rewrite message head- + ers from remote SMTP clients only if the client matches + the local_header_rewrite_clients parameter, or if the + remote_header_rewrite_domain configuration parameter spec- + ifies a non-empty value. To get the behavior before Post- + fix 2.2, specify "local_header_rewrite_clients = + static:all". + Typically, one would use the canonical(5) table to replace login names by Firstname.Lastname, or to clean up addresses produced by legacy mail systems. @@ -195,6 +203,15 @@ CANONICAL(5) CANONICAL(5) receives mail on. You need to stop and start Post- fix when this parameter changes. + local_header_rewrite_clients + Rewrite message header addresses in mail from these + clients and update incomplete addresses with the + domain name in $myorigin or $mydomain; either don't + rewrite message headers from other clients at all, + or rewrite message headers and update incomplete + addresses with the domain specified in the + remote_header_rewrite_domain parameter. + proxy_interfaces Other interfaces that this machine receives mail on by way of a proxy agent or network address transla- @@ -224,6 +241,12 @@ CANONICAL(5) CANONICAL(5) Give special treatment to owner-xxx and xxx-request addresses. + remote_header_rewrite_domain + Don't rewrite message headers from remote clients + at all when this parameter is empty; otherwise, re- + write message headers and append the specified + domain name to incomplete addresses. + SEE ALSO cleanup(8), canonicalize and enqueue mail postmap(1), Postfix lookup table manager @@ -235,7 +258,7 @@ CANONICAL(5) CANONICAL(5) ADDRESS_REWRITING_README, address rewriting guide LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. AUTHOR(S) diff --git a/postfix/html/cleanup.8.html b/postfix/html/cleanup.8.html index 4f187e1bd..792188942 100644 --- a/postfix/html/cleanup.8.html +++ b/postfix/html/cleanup.8.html @@ -245,8 +245,8 @@ CLEANUP(8) CLEANUP(8) remote_header_rewrite_domain (empty) Don't rewrite message headers from remote clients at all when this parameter is empty; otherwise, re- - write remote message headers and append the speci- - fied domain name to incomplete addresses. + write message headers and append the specified + domain name to incomplete addresses. RESOURCE AND RATE CONTROLS duplicate_filter_limit (1000) diff --git a/postfix/html/ldap_table.5.html b/postfix/html/ldap_table.5.html index 7c1c51e84..768dabb4a 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 behavior 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/local.8.html b/postfix/html/local.8.html index 1ed9eb5a9..c7c0f03c7 100644 --- a/postfix/html/local.8.html +++ b/postfix/html/local.8.html @@ -565,7 +565,7 @@ LOCAL(8) LOCAL(8) FILES The following are examples; details differ between systems. $HOME/.forward, per-user aliasing - /etc/aliases, sytem-wide alias database + /etc/aliases, system-wide alias database /var/spool/mail, system mailboxes SEE ALSO diff --git a/postfix/html/mysql_table.5.html b/postfix/html/mysql_table.5.html index 74c208892..23ed116ae 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 behavior 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..13152c9a5 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 absence 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 behavior 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..71800a22c 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:
+ ++ +
+ +- 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:
@@ -620,11 +638,11 @@ that is received by the Postfix mail system.-NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender.
-NOTE: automatic BCC recipients are produced only for new mail. +
Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself.
@@ -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:
+ ++ +
+ +- 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".
+ @@ -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:
+ ++ +
+ +- 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".
+ @@ -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:
+ ++ +
+ +- 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:
@@ -2144,7 +2216,7 @@ host, host:port, [host]:port, [address] or [address]:port; the form [host] turns off MX lookups. If you specify multiple SMTP destinations, Postfix will try them in the specified order. -NOTE: Do not use the fallback_relay feature when relaying mail +
Note: do not use the fallback_relay feature when relaying mail for a backup or primary MX domain. Mail would loop between the Postfix MX host and the fallback_relay host when the final destination is unavailable.
@@ -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.
@@ -3218,10 +3291,12 @@ into concurrency per domain.local_header_rewrite_clients (default: permit_inet_interfaces) - Append the domain name in $myorigin or $mydomain to message -header addresses from these clients only; either don't rewrite -message headers from other clients at all, or append the domain -specified with the remote_header_rewrite_domain parameter.
+Rewrite message header addresses in mail from these clients and +update incomplete addresses with the domain name in $myorigin or +$mydomain; either don't rewrite message headers from other clients +at all, or rewrite message headers and update incomplete addresses +with the domain specified in the remote_header_rewrite_domain +parameter.
See the append_at_myorigin and append_dot_mydomain parameters for details of how domain names are appended to incomplete addresses. @@ -3296,7 +3371,7 @@ from Postfix sendmail and in SMTP mail from this machine.
$myorigin or $mydomain information only with mail from Postfix sendmail, from local clients, or from authorized SMTP clients. -NOTE: This setting will not prevent remote mail header address +
Note: this setting will not prevent remote mail header address rewriting when mail from a remote client is forwarded by a neighboring system.
@@ -3464,7 +3539,7 @@ Note: luser_relay works only for the P-NOTE: if you use this feature for accounts not in the UNIX password +Note: if you use this feature for accounts not in the UNIX password file, then you must specify "local_recipient_maps =" (i.e. empty) in the main.cf file, otherwise the Postfix SMTP server will reject mail for non-UNIX accounts with "User unknown in local recipient table". @@ -3881,6 +3956,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:
+ ++ +
+ +- 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:
@@ -4967,11 +5060,11 @@ run "postmap /etc/postfix/recipient_bcc".-NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender.
-NOTE: automatic BCC recipients are produced only for new mail. +
Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself.
@@ -5140,7 +5233,7 @@ recipient addresses with $relay_r recipients. See also the relay domains address class in the ADDRESS_CLASS_README file. -NOTE: Postfix will not automatically forward mail for domains +
Note: Postfix will not automatically forward mail for domains that list this system as their primary or backup MX host. See the permit_mx_backup restriction in the postconf(5) manual page.
@@ -5304,8 +5397,8 @@ Examples: (default: empty)Don't rewrite message headers from remote clients at all when -this parameter is empty; otherwise, rewrite remote message headers -and append the specified domain name to incomplete addresses. The +this parameter is empty; otherwise, rewrite message headers and +append the specified domain name to incomplete addresses. The local_header_rewrite_clients parameter controls what clients Postfix considers local.
@@ -5457,11 +5550,11 @@ run "postmap /etc/postfix/sender_bcc".-NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender.
-NOTE: automatic BCC recipients are produced only for new mail. +
Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself.
@@ -6699,7 +6792,7 @@ TLS session cache. Specify a database type that supports enumeration, such as btree or sdbm; there is no need to support concurrent access. The file is created if it does not exist. -NOTE: dbm databases are not suitable. TLS +
Note: dbm databases are not suitable. TLS session objects are too large.
Example:
@@ -7200,7 +7293,7 @@ a restriction list, to make the default policy explicit.Reject the request when the envelope sender is the null address, and the message has multiple envelope recipients. Although this usage is technically allowed, it seems to have no legitimate -application. @@ -8832,7 +8925,7 @@ TLS session cache. Specify a database type that supports enumeration, such as btree or sdbm; there is no need to support concurrent access. The file is created if it does not exist. -
NOTE: this restriction can only work reliably +application.
Note: this restriction can only work reliably when used in smtpd_data_restrictions or smtpd_end_of_data_restrictions, because the total number of recipients is not known at an earlier stage of the SMTP conversation. @@ -7217,7 +7310,7 @@ of time where it is not allowed, or when the client sends SMTP commands ahead of time without knowing that Postfix actually supports ESMTP command pipelining. This stops mail from bulk mail software that improperly uses ESMTP command pipelining in order to speed up -deliveries.
NOTE: reject_unauth_pipelining is not useful +deliveries.
Note: reject_unauth_pipelining is not useful outside smtpd_data_restrictions when 1) the client uses ESMTP (EHLO instead of HELO) and 2) with "smtpd_delay_reject = yes" (the default). The use of reject_unauth_pipelining in the other @@ -7933,7 +8026,7 @@ system is the final destination. However, the SMTP server will not forward mail with addresses that have sender-specified routing information (example: user@elsewhere@domain). Use the optional permit_mx_backup_networks parameter to require that the primary -MX hosts match a list of network blocks.
NOTE: prior to +MX hosts match a list of network blocks.
Note: prior to Postfix version 2.0, use of permit_mx_backup is not recommended; mail may be rejected in case of a temporary DNS lookup problem.NOTE: dbm databases are not suitable. TLS +
Note: dbm databases are not suitable. TLS session objects are too large.
Example:
@@ -9044,6 +9137,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:
@@ -9690,7 +9801,7 @@ such deliveries are safe without application-level locks.-Note 1: The dotlock method requires that the recipient UID +Note 1: the dotlock method requires that the recipient UID or GID has write access to the parent directory of the recipient's mailbox file.
diff --git a/postfix/html/smtpd.8.html b/postfix/html/smtpd.8.html index 6af8d162b..a6362559e 100644 --- a/postfix/html/smtpd.8.html +++ b/postfix/html/smtpd.8.html @@ -130,11 +130,13 @@ SMTPD(8) SMTPD(8) Available in Postfix version 2.2 and later: local_header_rewrite_clients (permit_inet_interfaces) - Append the domain name in $myorigin or $mydomain to - message header addresses from these clients only; - either don't rewrite message headers from other - clients at all, or append the domain specified with - the remote_header_rewrite_domain parameter. + Rewrite message header addresses in mail from these + clients and update incomplete addresses with the + domain name in $myorigin or $mydomain; either don't + rewrite message headers from other clients at all, + or rewrite message headers and update incomplete + addresses with the domain specified in the + remote_header_rewrite_domain parameter. AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS As of version 1.0, Postfix can be configured to send new diff --git a/postfix/man/man5/canonical.5 b/postfix/man/man5/canonical.5 index 36ce43938..4cf4253c5 100644 --- a/postfix/man/man5/canonical.5 +++ b/postfix/man/man5/canonical.5 @@ -44,6 +44,13 @@ that are used in SMTP protocol commands). Think Sendmail rule set \fBS3\fR, if you like. This is controlled with the \fBcanonical_classes\fR 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, 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". + Typically, one would use the \fBcanonical\fR(5) table to replace login names by \fIFirstname.Lastname\fR, or to clean up addresses produced by legacy mail systems. @@ -191,6 +198,13 @@ Other parameters of interest: .IP \fBinet_interfaces\fR The network interface addresses that this system receives mail on. You need to stop and start Postfix when this parameter changes. +.IP \fBlocal_header_rewrite_clients\fR +Rewrite message header addresses in mail from these clients +and update incomplete addresses with the domain name in +$myorigin or $mydomain; either don't rewrite message headers +from other clients at all, or rewrite message headers and +update incomplete addresses with the domain specified in +the remote_header_rewrite_domain parameter. .IP \fBproxy_interfaces\fR Other interfaces that this machine receives mail on by way of a proxy agent or network address translator. @@ -209,6 +223,11 @@ The domain that is appended to locally-posted mail. .IP \fBowner_request_special\fR Give special treatment to \fBowner-\fIxxx\fR and \fIxxx\fB-request\fR addresses. +.IP \fBremote_header_rewrite_domain\fR +Don't rewrite message headers from remote clients at all +when this parameter is empty; otherwise, rewrite message +headers and append the specified domain name to incomplete +addresses. .SH "SEE ALSO" .na .nf diff --git a/postfix/man/man5/ldap_table.5 b/postfix/man/man5/ldap_table.5 index 594f4c4e6..3aa9e1e63 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 +behavior is identical to that described with \fBquery_filter\fR, +and in fact because the input key is known in advance, lookups +whose key does not contain all the information specified in +the result template are suppressed and return no results. +.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..b3f3c3eb2 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 +behavior 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..ccf886457 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 absence 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 +behavior 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..4e3570cd5 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 @@ -338,10 +352,10 @@ and \fBpostdrop\fR(1). Optional address that receives a "blind carbon copy" of each message that is received by the Postfix mail system. .PP -NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender. .PP -NOTE: automatic BCC recipients are produced only for new mail. +Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself. @@ -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 @@ -1116,7 +1172,7 @@ host, host:port, [host]:port, [address] or [address]:port; the form [host] turns off MX lookups. If you specify multiple SMTP destinations, Postfix will try them in the specified order. .PP -NOTE: Do not use the fallback_relay feature when relaying mail +Note: do not use the fallback_relay feature when relaying mail for a backup or primary MX domain. Mail would loop between the Postfix MX host and the fallback_relay host when the final destination is unavailable. @@ -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 @@ -1680,10 +1737,12 @@ Setting this parameter to a value > 1 changes the meaning of local_destination_concurrency_limit from concurrency per recipient into concurrency per domain. .SH local_header_rewrite_clients (default: permit_inet_interfaces) -Append the domain name in $myorigin or $mydomain to message -header addresses from these clients only; either don't rewrite -message headers from other clients at all, or append the domain -specified with the remote_header_rewrite_domain parameter. +Rewrite message header addresses in mail from these clients and +update incomplete addresses with the domain name in $myorigin or +$mydomain; either don't rewrite message headers from other clients +at all, or rewrite message headers and update incomplete addresses +with the domain specified in the remote_header_rewrite_domain +parameter. .PP See the append_at_myorigin and append_dot_mydomain parameters for details of how domain names are appended to incomplete addresses. @@ -1748,7 +1807,7 @@ The intermediate setting: rewrite header addresses and append $myorigin or $mydomain information only with mail from Postfix sendmail, from local clients, or from authorized SMTP clients. .PP -NOTE: This setting will not prevent remote mail header address +Note: this setting will not prevent remote mail header address rewriting when mail from a remote client is forwarded by a neighboring system. .PP @@ -1851,7 +1910,7 @@ Instead of $name you can also specify ${name} or $(name). .PP Note: luser_relay works only for the Postfix \fBlocal\fR(8) delivery agent. .PP -NOTE: if you use this feature for accounts not in the UNIX password +Note: if you use this feature for accounts not in the UNIX password file, then you must specify "local_recipient_maps =" (i.e. empty) in the main.cf file, otherwise the Postfix SMTP server will reject mail for non-UNIX accounts with "User unknown in local recipient table". @@ -2077,6 +2136,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 @@ -2668,10 +2741,10 @@ Look up the "@domain.tld" part. Specify the types and names of databases to use. After change, run "\fBpostmap /etc/postfix/recipient_bcc\fR". .PP -NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender. .PP -NOTE: automatic BCC recipients are produced only for new mail. +Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself. @@ -2791,7 +2864,7 @@ recipient addresses with $relay_recipient_maps and rejects non-existent recipients. See also the relay domains address class in the ADDRESS_CLASS_README file. .PP -NOTE: Postfix will not automatically forward mail for domains +Note: Postfix will not automatically forward mail for domains that list this system as their primary or backup MX host. See the permit_mx_backup restriction in the \fBpostconf\fR(5) manual page. .PP @@ -2895,8 +2968,8 @@ relocated_maps = hash:/etc/postfix/relocated .ft R .SH remote_header_rewrite_domain (default: empty) Don't rewrite message headers from remote clients at all when -this parameter is empty; otherwise, rewrite remote message headers -and append the specified domain name to incomplete addresses. The +this parameter is empty; otherwise, rewrite message headers and +append the specified domain name to incomplete addresses. The local_header_rewrite_clients parameter controls what clients Postfix considers local. .PP @@ -2993,10 +3066,10 @@ Look up the "@domain.tld" part. Specify the types and names of databases to use. After change, run "\fBpostmap /etc/postfix/sender_bcc\fR". .PP -NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender. .PP -NOTE: automatic BCC recipients are produced only for new mail. +Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself. @@ -3714,7 +3787,7 @@ TLS session cache. Specify a database type that supports enumeration, such as \fBbtree\fR or \fBsdbm\fR; there is no need to support concurrent access. The file is created if it does not exist. .PP -NOTE: \fBdbm\fR databases are not suitable. TLS +Note: \fBdbm\fR databases are not suitable. TLS session objects are too large. .PP Example: @@ -4041,7 +4114,7 @@ and the message has multiple envelope recipients. Although this usage is technically allowed, it seems to have no legitimate application. .br -NOTE: this restriction can only work reliably +Note: this restriction can only work reliably when used in smtpd_data_restrictions or smtpd_end_of_data_restrictions, because the total number of recipients is not known at an earlier stage of the SMTP conversation. @@ -4058,7 +4131,7 @@ ESMTP command pipelining. This stops mail from bulk mail software that improperly uses ESMTP command pipelining in order to speed up deliveries. .br -NOTE: reject_unauth_pipelining is not useful +Note: reject_unauth_pipelining is not useful outside smtpd_data_restrictions when 1) the client uses ESMTP (EHLO instead of HELO) and 2) with "smtpd_delay_reject = yes" (the default). The use of reject_unauth_pipelining in the other @@ -4477,7 +4550,7 @@ information (example: user@elsewhere@domain). Use the optional permit_mx_backup_networks parameter to require that the primary MX hosts match a list of network blocks. .br -NOTE: prior to +Note: prior to Postfix version 2.0, use of permit_mx_backup is not recommended; mail may be rejected in case of a temporary DNS lookup problem. .IP "\fBreject_non_fqdn_recipient\fR" @@ -5099,7 +5172,7 @@ TLS session cache. Specify a database type that supports enumeration, such as \fBbtree\fR or \fBsdbm\fR; there is no need to support concurrent access. The file is created if it does not exist. .PP -NOTE: \fBdbm\fR databases are not suitable. TLS +Note: \fBdbm\fR databases are not suitable. TLS session objects are too large. .PP Example: @@ -5205,6 +5278,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 @@ -5544,7 +5631,7 @@ delivery. For a list of available file locking methods, use the This setting is ignored with \fBmaildir\fR style delivery, because such deliveries are safe without application-level locks. .PP -Note 1: The \fBdotlock\fR method requires that the recipient UID +Note 1: the \fBdotlock\fR method requires that the recipient UID or GID has write access to the parent directory of the recipient's mailbox file. .PP diff --git a/postfix/man/man8/cleanup.8 b/postfix/man/man8/cleanup.8 index 0093e4113..f1cce61e6 100644 --- a/postfix/man/man8/cleanup.8 +++ b/postfix/man/man8/cleanup.8 @@ -209,8 +209,8 @@ What addresses are subject to sender_canonical_maps address mapping. .IP "\fBremote_header_rewrite_domain (empty)\fR" Don't rewrite message headers from remote clients at all when -this parameter is empty; otherwise, rewrite remote message headers -and append the specified domain name to incomplete addresses. +this parameter is empty; otherwise, rewrite message headers and +append the specified domain name to incomplete addresses. .SH "RESOURCE AND RATE CONTROLS" .na .nf diff --git a/postfix/man/man8/local.8 b/postfix/man/man8/local.8 index 00156aa8f..7165dddf3 100644 --- a/postfix/man/man8/local.8 +++ b/postfix/man/man8/local.8 @@ -537,7 +537,7 @@ records, so that "smtpd" becomes, for example, "postfix/smtpd". .nf The following are examples; details differ between systems. $HOME/.forward, per-user aliasing -/etc/aliases, sytem-wide alias database +/etc/aliases, system-wide alias database /var/spool/mail, system mailboxes .SH "SEE ALSO" .na diff --git a/postfix/man/man8/smtpd.8 b/postfix/man/man8/smtpd.8 index 194cb0266..1a5b2b35a 100644 --- a/postfix/man/man8/smtpd.8 +++ b/postfix/man/man8/smtpd.8 @@ -127,10 +127,12 @@ filtering, or address mapping. .PP Available in Postfix version 2.2 and later: .IP "\fBlocal_header_rewrite_clients (permit_inet_interfaces)\fR" -Append the domain name in $myorigin or $mydomain to message -header addresses from these clients only; either don't rewrite -message headers from other clients at all, or append the domain -specified with the remote_header_rewrite_domain parameter. +Rewrite message header addresses in mail from these clients and +update incomplete addresses with the domain name in $myorigin or +$mydomain; either don't rewrite message headers from other clients +at all, or rewrite message headers and update incomplete addresses +with the domain specified in the remote_header_rewrite_domain +parameter. .SH "AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS" .na .nf diff --git a/postfix/proto/ADDRESS_REWRITING_README.html b/postfix/proto/ADDRESS_REWRITING_README.html index b681db624..4955a6e8b 100644 --- a/postfix/proto/ADDRESS_REWRITING_README.html +++ b/postfix/proto/ADDRESS_REWRITING_README.html @@ -153,21 +153,21 @@ how it works:-
@@ -317,15 +317,18 @@ turn-off control- Postfix always rewrites message header addresses from local -SMTP clients, and from the Postfix sendmail command. The -local_header_rewrite_clients parameter controls what SMTP clients -Postfix considers local (by default, only local network interface -addresses). +
- Postfix always rewrites message headers from local SMTP clients +and from the Postfix sendmail command, and appends its own domain +to incomplete addresses. The local_header_rewrite_clients parameter +controls what SMTP clients Postfix considers local (by default, +only local network interface addresses).
- Postfix never rewrites message header addresses from remote SMTP clients when the remote_header_rewrite_domain parameter value is empty (the default setting). -
- Otherwise, Postfix appends the remote_header_rewrite_domain -value to incomplete message header addresses from remote SMTP -clients. This feature can be used to append a reserved domain such -as "domain.invalid", so that incomplete addresses cannot be mistaken -for local addresses. +
- Otherwise, Postfix rewrites message headers from remote SMTP +clients, and appends the remote_header_rewrite_domain value to +incomplete addresses. This feature can be used to append a reserved +domain such as "domain.invalid", so that incomplete addresses cannot +be mistaken for local addresses.
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.
-The Postfix trivial-rewrite(8) daemon implements the following hard-coded address manipulations:
@@ -430,21 +427,41 @@ hard-coded address manipulations: is called a route address, and specifies that mail for "user@site" be delivered via "hosta" and "hostb". Usage of this form has been deprecated for a long time. Postfix has no ability to handle route -addresses, other than to strip off the route part. +addresses, other than to strip off the route part. + +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".
This feature is controlled by the boolean swap_bangpath parameter (default: yes). The purpose is to rewrite UUCP-style addresses to domain style. This is useful only when you receive -mail via UUCP, but it probably does not hurt otherwise.
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".
This feature is controlled by the boolean allow_percent_hack parameter (default: yes). Typically, this is used in order to deal with monstrosities such as "user%domain@otherdomain".
-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".
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 +496,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 @@ -489,7 +512,14 @@ of having Postfix's own domain appended automatically.
A single trailing dot is silently removed. However, an address that ends in multiple dots will be rejected as an invalid -address.
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".
@@ -502,11 +532,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 +616,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.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'
- 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.
Using mirrored databases
@@ -130,17 +120,24 @@ those hosts is reachable.diff --git a/postfix/proto/STANDARD_CONFIGURATION_README.html b/postfix/proto/STANDARD_CONFIGURATION_README.html index e97ff32fb..9b0ae01cb 100644 --- a/postfix/proto/STANDARD_CONFIGURATION_README.html +++ b/postfix/proto/STANDARD_CONFIGURATION_README.html @@ -430,7 +430,7 @@ listening on the internal interface. In such a configuration is it is tempting to configure $inet_interfaces in each instance with just the corresponding interface address. -
- 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.
In most cases using inet_interaces in this way will not work, +
In most cases, using inet_interfaces in this way will not work, because as documented in the $inet_interfaces reference manual, the smtp(8) delivery agent will also use the specified interface address as the source address for outbound connections and will be unable to diff --git a/postfix/proto/canonical b/postfix/proto/canonical index 2d7f17b72..fa04fbf74 100644 --- a/postfix/proto/canonical +++ b/postfix/proto/canonical @@ -38,6 +38,13 @@ # rule set \fBS3\fR, if you like. This is controlled with # the \fBcanonical_classes\fR 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, 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". +# # Typically, one would use the \fBcanonical\fR(5) table to replace login # names by \fIFirstname.Lastname\fR, or to clean up addresses produced # by legacy mail systems. @@ -169,6 +176,13 @@ # .IP \fBinet_interfaces\fR # The network interface addresses that this system receives mail on. # You need to stop and start Postfix when this parameter changes. +# .IP \fBlocal_header_rewrite_clients\fR +# Rewrite message header addresses in mail from these clients +# and update incomplete addresses with the domain name in +# $myorigin or $mydomain; either don't rewrite message headers +# from other clients at all, or rewrite message headers and +# update incomplete addresses with the domain specified in +# the remote_header_rewrite_domain parameter. # .IP \fBproxy_interfaces\fR # Other interfaces that this machine receives mail on by way of a # proxy agent or network address translator. @@ -187,6 +201,11 @@ # .IP \fBowner_request_special\fR # Give special treatment to \fBowner-\fIxxx\fR and \fIxxx\fB-request\fR # addresses. +# .IP \fBremote_header_rewrite_domain\fR +# Don't rewrite message headers from remote clients at all +# when this parameter is empty; otherwise, rewrite message +# headers and append the specified domain name to incomplete +# addresses. # SEE ALSO # cleanup(8), canonicalize and enqueue mail # postmap(1), Postfix lookup table manager diff --git a/postfix/proto/ldap_table b/postfix/proto/ldap_table index 3c840c309..13dc54972 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 +# behavior is identical to that described with \fBquery_filter\fR, +# and in fact because the input key is known in advance, lookups +# whose key does not contain all the information specified in +# the result template are suppressed and return no results. +# .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..1ddc07aa9 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 +# behavior 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..074ec7f0f 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 absence 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 +# behavior 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..83eedcdda 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:
@@ -546,11 +564,11 @@ that is received by the Postfix mail system.-NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender.
-NOTE: automatic BCC recipients are produced only for new mail. +
Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself.
@@ -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:
@@ -1181,7 +1217,7 @@ host, host:port, [host]:port, [address] or [address]:port; the form [host] turns off MX lookups. If you specify multiple SMTP destinations, Postfix will try them in the specified order. -NOTE: Do not use the fallback_relay feature when relaying mail +
Note: do not use the fallback_relay feature when relaying mail for a backup or primary MX domain. Mail would loop between the Postfix MX host and the fallback_relay host when the final destination is unavailable.
@@ -1638,7 +1674,8 @@ inet_interfaces = 192.168.1.2, 127.0.0.1The 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.
@@ -2109,7 +2146,7 @@ Note: luser_relay works only for the Postfix local(8) delivery agent.-NOTE: if you use this feature for accounts not in the UNIX password +Note: if you use this feature for accounts not in the UNIX password file, then you must specify "local_recipient_maps =" (i.e. empty) in the main.cf file, otherwise the Postfix SMTP server will reject mail for non-UNIX accounts with "User unknown in local recipient table". @@ -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:
@@ -3038,11 +3094,11 @@ run "postmap /etc/postfix/recipient_bcc".-NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender.
-NOTE: automatic BCC recipients are produced only for new mail. +
Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself.
@@ -3119,7 +3175,7 @@ recipient addresses with $relay_recipient_maps and rejects non-existent recipients. See also the relay domains address class in the ADDRESS_CLASS_README file. -NOTE: Postfix will not automatically forward mail for domains +
Note: Postfix will not automatically forward mail for domains that list this system as their primary or backup MX host. See the permit_mx_backup restriction in the postconf(5) manual page.
@@ -3311,11 +3367,11 @@ run "postmap /etc/postfix/sender_bcc".-NOTE: if mail to the BCC address bounces it will be returned to +Note: if mail to the BCC address bounces it will be returned to the sender.
-NOTE: automatic BCC recipients are produced only for new mail. +
Note: automatic BCC recipients are produced only for new mail. To avoid mailer loops, automatic BCC recipients are not generated for mail that Postfix forwards internally, nor for mail that Postfix generates itself.
@@ -4444,7 +4500,7 @@ a restriction list, to make the default policy explicit.Reject the request when the envelope sender is the null address, and the message has multiple envelope recipients. Although this usage is technically allowed, it seems to have no legitimate -application. @@ -5478,6 +5534,24 @@ necessary if your machine is connected to UUCP networks. It is enabled by default. +
NOTE: this restriction can only work reliably +application.
Note: this restriction can only work reliably when used in smtpd_data_restrictions or smtpd_end_of_data_restrictions, because the total number of recipients is not known at an earlier stage of the SMTP conversation. @@ -4461,7 +4517,7 @@ of time where it is not allowed, or when the client sends SMTP commands ahead of time without knowing that Postfix actually supports ESMTP command pipelining. This stops mail from bulk mail software that improperly uses ESMTP command pipelining in order to speed up -deliveries.
NOTE: reject_unauth_pipelining is not useful +deliveries.
Note: reject_unauth_pipelining is not useful outside smtpd_data_restrictions when 1) the client uses ESMTP (EHLO instead of HELO) and 2) with "smtpd_delay_reject = yes" (the default). The use of reject_unauth_pipelining in the other @@ -4977,7 +5033,7 @@ system is the final destination. However, the SMTP server will not forward mail with addresses that have sender-specified routing information (example: user@elsewhere@domain). Use the optional permit_mx_backup_networks parameter to require that the primary -MX hosts match a list of network blocks.
NOTE: prior to +MX hosts match a list of network blocks.
Note: prior to Postfix version 2.0, use of permit_mx_backup is not recommended; mail may be rejected in case of a temporary DNS lookup problem.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@@ -7340,7 +7450,7 @@ such deliveries are safe without application-level locks.
-Note 1: The dotlock method requires that the recipient UID +Note 1: the dotlock method requires that the recipient UID or GID has write access to the parent directory of the recipient's mailbox file.
@@ -7555,8 +7665,8 @@ physical endpoints. %PARAM remote_header_rewrite_domainDon't rewrite message headers from remote clients at all when -this parameter is empty; otherwise, rewrite remote message headers -and append the specified domain name to incomplete addresses. The +this parameter is empty; otherwise, rewrite message headers and +append the specified domain name to incomplete addresses. The local_header_rewrite_clients parameter controls what clients Postfix considers local.
@@ -7579,10 +7689,12 @@ clients at all. %PARAM local_header_rewrite_clients permit_inet_interfaces -Append the domain name in $myorigin or $mydomain to message -header addresses from these clients only; either don't rewrite -message headers from other clients at all, or append the domain -specified with the remote_header_rewrite_domain parameter.
+Rewrite message header addresses in mail from these clients and +update incomplete addresses with the domain name in $myorigin or +$mydomain; either don't rewrite message headers from other clients +at all, or rewrite message headers and update incomplete addresses +with the domain specified in the remote_header_rewrite_domain +parameter.
See the append_at_myorigin and append_dot_mydomain parameters for details of how domain names are appended to incomplete addresses. @@ -7657,7 +7769,7 @@ from Postfix sendmail and in SMTP mail from this machine.
$myorigin or $mydomain information only with mail from Postfix sendmail, from local clients, or from authorized SMTP clients. -NOTE: This setting will not prevent remote mail header address +
Note: this setting will not prevent remote mail header address rewriting when mail from a remote client is forwarded by a neighboring system.
@@ -7879,7 +7991,7 @@ TLS session cache. Specify a database type that supports enumeration, such as btree or sdbm; there is no need to support concurrent access. The file is created if it does not exist. -NOTE: dbm databases are not suitable. TLS +
Note: dbm databases are not suitable. TLS session objects are too large.
Example:
@@ -8076,7 +8188,7 @@ TLS session cache. Specify a database type that supports enumeration, such as btree or sdbm; there is no need to support concurrent access. The file is created if it does not exist. -NOTE: dbm databases are not suitable. TLS +
Note: dbm databases are not suitable. TLS session objects are too large.
Example:
diff --git a/postfix/src/cleanup/cleanup.c b/postfix/src/cleanup/cleanup.c index a8a3ee083..720e4fe97 100644 --- a/postfix/src/cleanup/cleanup.c +++ b/postfix/src/cleanup/cleanup.c @@ -185,8 +185,8 @@ /* mapping. /* .IP "\fBremote_header_rewrite_domain (empty)\fR" /* Don't rewrite message headers from remote clients at all when -/* this parameter is empty; otherwise, rewrite remote message headers -/* and append the specified domain name to incomplete addresses. +/* this parameter is empty; otherwise, rewrite message headers and +/* append the specified domain name to incomplete addresses. /* RESOURCE AND RATE CONTROLS /* .ad /* .fi 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 5ba0f7925..7f13ed803 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,14 +20,14 @@ * 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 "20050309" +#define MAIL_VERSION_NUMBER "2.2.0" #define VAR_MAIL_VERSION "mail_version" #ifdef SNAPSHOT #define DEF_MAIL_VERSION MAIL_VERSION_NUMBER "-" MAIL_RELEASE_DATE #else -#define DEF_MAIL_VERSION MAIL_VERSION_NUMBER "-RC2" +#define DEF_MAIL_VERSION MAIL_VERSION_NUMBER #endif extern char *var_mail_version; diff --git a/postfix/src/local/local.c b/postfix/src/local/local.c index 413b6fdce..7dfc6770e 100644 --- a/postfix/src/local/local.c +++ b/postfix/src/local/local.c @@ -493,7 +493,7 @@ /* FILES /* The following are examples; details differ between systems. /* $HOME/.forward, per-user aliasing -/* /etc/aliases, sytem-wide alias database +/* /etc/aliases, system-wide alias database /* /var/spool/mail, system mailboxes /* SEE ALSO /* qmgr(8), queue manager 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.c b/postfix/src/smtpd/smtpd.c index 811f75c26..756a4cd93 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -109,10 +109,12 @@ /* .PP /* Available in Postfix version 2.2 and later: /* .IP "\fBlocal_header_rewrite_clients (permit_inet_interfaces)\fR" -/* Append the domain name in $myorigin or $mydomain to message -/* header addresses from these clients only; either don't rewrite -/* message headers from other clients at all, or append the domain -/* specified with the remote_header_rewrite_domain parameter. +/* Rewrite message header addresses in mail from these clients and +/* update incomplete addresses with the domain name in $myorigin or +/* $mydomain; either don't rewrite message headers from other clients +/* at all, or rewrite message headers and update incomplete addresses +/* with the domain specified in the remote_header_rewrite_domain +/* parameter. /* AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS /* .ad /* .fi 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. *