From: Wietse Venema Date: Wed, 2 Jul 2003 05:00:00 +0000 (-0500) Subject: postfix-2.0.13-20030702 X-Git-Tag: v2.1-RC1-20040331~54 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5884ef624c410d77da2da28c012714b44dca114b;p=thirdparty%2Fpostfix.git postfix-2.0.13-20030702 --- diff --git a/postfix/.indent.pro b/postfix/.indent.pro index c662f44f5..2388a60ed 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -26,6 +26,8 @@ -TDELIVER_ATTR -TDELIVER_REQUEST -TDICT +-TDICT_CIDR +-TDICT_CIDR_ENTRY -TDICT_DB -TDICT_DBM -TDICT_DEBUG diff --git a/postfix/HISTORY b/postfix/HISTORY index 0984941cc..6f4122a0d 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -8189,8 +8189,8 @@ Apologies for any names omitted. of mail probes, so it will no longer block for in_flow_delay seconds when mail arrives faster than it is delivered. Still need to make mail_stream_finish() asynchronous in - order to avoid blocking for trigger_timeout seconds when - the queue manager is overwhelmed. Files: global/post_mail.c, + order to avoid blocking for trigger_timeout seconds when the + queue manager is overwhelmed. Files: global/post_mail.c, verify/verify.c. Bugfix: removed extraneous sleep() after the last attempt @@ -8201,6 +8201,42 @@ Apologies for any names omitted. Bugfix: the stricter postdrop input filter broke "sendmail -bs". Found by Lutz Jaenicke. File: smtpd/smtpd.c. +20030614 + + Portability: Dropped support for client side LDAP caching. + As of release 2.1.13 OpenLDAP no longer supports client + side caching, it has been deprecated for some time, and + never worked well. Implemented by Victor Duchovni, Morgan + Stanley, and further enhanced by Lamont Jones, HP. Files: + src/util/dict_ldap.c, conf/sample-ldap.cf, + README_FILES/LDAP_README. + + Safety: Given suitable invalid database contents, LDAP + lookups can produce too many results, enter an infinite + loop in the expansion of "special result attributes" (LDAP + DNs and LDAP URLs) or just consume excessive server resources + returning large result sets. Three new (per LDAP map) + configuration parameters enable one to set limits on + recursive nesting, result expansion and the server response + "entry" count. Implemented by Victor Duchovni, Morgan + Stanley, further enanced by Lamont Jones, HP. Files: + src/util/dict_ldap.c, conf/sample-ldap.cf, + README_FILES/LDAP_README. + +20030616 + + Feature: in mail delivery status reports, report the sender + address as X-Postfix-Sender. Matthias Andree. File: + bounce/bounce_notify_util.c. + + Cleanup: in mail delivery status reports, transform the + original recipient into xtext format as required by RFC + 1891. Files: bounce/bounce_notify_util.c, util/xtext.[hc]. + + Cleanup: more accurate "postfix check" warning for files + that miss one or more of the required mode 02111 execute + permission bits. Matthias Andree. File: conf/postfix-script. + 20030618 After "postfix reload", the master daemon now warns when @@ -8208,6 +8244,20 @@ Apologies for any names omitted. of passing incorrect information to the smtp server. File: master/master_ent.c. +20030619 + + Feature: the Postfix SMTP server can send all mail into a + proxy server, for example a real-time SPAM filter. This + proxy is supposed to send the mail into another Postfix + SMTP server process for normal delivery. Files: smtpd/smtpd.c + smtpd/smtpd_proxy.[hc]. + +20030620 + + Bugfix: a cut-and-paste error caused the proxy server's + 354 status code to be reported when a proxy connection + broke during the DATA phase. File: smtpd.c. + 20030620 Bugfix: after the last change to postdrop, postcat no longer @@ -8217,6 +8267,74 @@ Apologies for any names omitted. sendmail, "-t" broke multi-line recipient headers. Victor Duchovni, Morgan Stanley. File: sendmail/sendmail.c. +20030621 + + Workaround: the safe_open(O_CREAT) race condition exploit + avoiding code tries a little harder when it encounters a + race condition. File: util/safe_open.c. + +20030623 + + Non-prod operator precedence bug with detecting end of + DATA. Matthias Andree. File: smtpd/smtpd.c. + +20030624 + + Bugfix: reject_unverified_address() set the defer_if_reject + flag when the verify service was unavailable (which never + happens). Victor Duchovni, Morgan Stanley. File: + smtpd/smtpd_check.c. + + New parameters address_verify_poll_{count,delay} that + control how often to poll the address verification service + for the completion of an address verification request. + Specify address_verify_poll_count=1 to implement a crude + form of greylisting, that is, always defer the first delivery + attempt for an unknown address. File: smtpd/smtpd_check.c. + + Bugfix: after the last change to postdrop, postcat no longer + recognized non-maildrop queue files as valid. File: + postcat/postcat.c. + +20030629 + + Cleanup: replaced references to "simulated virtual domains" + by "virtual alias domains". Victor Duchovni, Morgan Stanley. + +20030630 + + Feature: smtp_quote_rfc821_envelope=(yes|no) to control + RFC 821 style quoting of MAIL FROM and RCPT TO addresses. + Files: global/mail_params.h, smtp/smtp.c, smtp/smtp_proto.c. + +20030701 + + Bugfix: multi-recipient probes triggered a bug in the SMTP + client. File: smtp/smtp_proto.c. + + Feature: enable_original_recipient (default: yes) to control + whether Postfix keeps track of original recipient address + information. Victor Duchovni, Morgan Stanley. Files: + cleanup/cleanup.c, cleanup/cleanup_init.c, + cleanup/cleanup_out_recipient.c, global/log_adhoc.c, + global/mail_copy.c, *qmgr/qmgr_message.c. + + Feature: !/pattern/ support for PCRE lookup tables. Victor + Duchovni, Morgan Stanley. Files: util/dict_pcre.c. + + Cleanup: allow whitespace after patterns in repexp and pcre + tables. Victor Duchovni, Morgan Stanley. Files: + util/dict_pcre.c, util/dict_regexp.c. + +20030702 + + Feature: CIDR lookup table support, very remotely based on + code by Jozsef Kadlecsik. Files: proto/cidr_table, + util/dict_cidr.[hc]. + + Feature: TCP lookup table support, finally finished. Files: + proto/tcp_table, proto/dict_tcp.[hc]. + Open problems: Low: smtp-source may block when sending large test messages. diff --git a/postfix/README_FILES/LDAP_README b/postfix/README_FILES/LDAP_README index 02c152b3c..abf8fd5d9 100644 --- a/postfix/README_FILES/LDAP_README +++ b/postfix/README_FILES/LDAP_README @@ -154,21 +154,41 @@ parameter below, "server_host", would be defined in main.cf as the Postfix user. Example: ldapsource_bind_pw = postfixpw - cache (no) - Whether to use a client-side cache for the LDAP connection. See - ldap_enable_cache(3). It's off by default. - - cache_expiry (30 seconds) - If the client-side cache is enabled, cached results will expire - after this many seconds. - - cache_size (32768 bytes) - If the client-side cache is enabled, this is its size in bytes. + cache (IGNORED with a warning) + cache_expiry (IGNORED with a warning) + cache_size (IGNORED with a warning) + The above parameters are NO LONGER SUPPORTED by Postfix. + Cache support has been dropped from OpenLDAP as of release 2.1.13. + + recursion_limit (1000) + 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 (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. + + size_limit ($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. 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 + utilization on the LDAP server, not the final multiplicity of the + lookup result. It is analogous to the "-z" option of "ldapsearch". dereference (0) - 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 implementations: + 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 implementations: 0 never 1 when searching diff --git a/postfix/README_FILES/SMTPD_PROXY_README b/postfix/README_FILES/SMTPD_PROXY_README new file mode 100644 index 000000000..28e1e9eb5 --- /dev/null +++ b/postfix/README_FILES/SMTPD_PROXY_README @@ -0,0 +1,81 @@ +Purpose of the SMTPD pass-through proxy feature +=============================================== + +The Postfix SMTP server can be configured to forward all mail to +a proxy server, for example, a real-time SPAM filter. The proxy is +supposed to send the mail into another Postfix SMTP server process +for normal delivery. + +The proxy server receives only the commands that the Postfix SMTP +server has approved. The proxy server should accept the same MAIL +FROM and RCPT TO command syntax as Postfix, but does not need to +support ESMTP command pipelining. + +This feature is meant to be used as follows: + + Internet -> smtpd -> proxy -> smtpd -> cleanup -> queue + Postfix Postfix Postfix Postfix + +Limitations +=========== + +When used with a real-time SPAM filter, this approach allows Postfix +to reject mail before the SMTP mail transfer completes, so that +Postfix does not have to send rejected mail back to the sender. +Mail that is not accepted remains the responsibility of the client. + +In all other respects this content filtering approach is inferior +to the existing content filter (see FILTER_README) which processes +mail AFTER it is queued, because that gives you full control over +how many filtering processes can be run in parallel. + +The problem with real-time content filtering is that the remote +SMTP client expects an SMTP reply within a deadline. As the system +load increases, fewer and fewer CPU cycles remain available to +answer within the deadline, and eventually you either have to stop +accepting mail or you have to accept unfiltered mail. + +A possible workaround is to have the proxy take special action when +the deadline is reached: add a distinctive message header that +triggers a Postfix header_checks FILTER action, or send the mail +into Postfix via an alternative Postfix SMTP server that always +turns on content filtering. + +Configuration parameters +======================== + +Parameters that control proxying: + +smtpd_proxy_filter (syntax: host:port) + + The host and TCP port of the SMTP proxy server. When no host + or host: is specified, localhost is assumed. + +smtpd_proxy_timeout (default: 100s) + + Timeout for connecting to the SMTP proxy server and for sending + and receiving data. All proxy errors are logged to the maillog + file, but the client sees "451 Error: queue file write error". + +smtpd_proxy_ehlo (default: $myhostname) + + The hostname to use when sending an EHLO command to the SMTP + proxy server. + +Testing the SMTP pass-through proxy feature +=========================================== + +The following example sets up a null proxy, that is, the Postfix +SMTP server gives the mail directly to another Postfix SMTP server +process. + +/etc/postfix/master.cf + smtp inet n - n - - smtpd + -o smtpd_proxy_filter=26 + 26 inet n - n - - smtpd + +The result is as follows: + + Internet -> smtpd on port 25 -> smtpd on port 26 -> cleanup -> queue + +This configuration is sufficient for stress testing. diff --git a/postfix/README_FILES/VIRTUAL_README b/postfix/README_FILES/VIRTUAL_README index 3a4220e12..44f2e1407 100644 --- a/postfix/README_FILES/VIRTUAL_README +++ b/postfix/README_FILES/VIRTUAL_README @@ -8,7 +8,7 @@ Purpose of this software You can use the virtual delivery agent for mailbox delivery of some or all domains that are handled by a machine. -This mechanism is different from simulated virtual domains. Those +This mechanism is different from virtual alias domains. Those are implemented by translating every recipient address into a different address. For that, see the virtual(5) manual page. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 5dcc56686..0221d767d 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -22,6 +22,51 @@ snapshot release). Patches change the patchlevel and the release date. Snapshots change only the release date, unless they include the same bugfixes as a patch release. +Incompatible changes with Postfix snapshot 2.0.13-20030702 +========================================================== + +Support for client side LDAP caching is gone. OpenLDAP 2.1.13 and +later no longer support it, and the feature never worked well. +Postfix now ignores cache controlling parameters in an LDAP +configuration file and logs a warning. Credits to Victor Duchovni +and Lamont Jones. + +Major changes with Postfix snapshot 2.0.13-20030702 +=================================================== + +The Postfix SMTP server can be configured to send all mail into a +proxy server, for example a real-time SPAM filter. This proxy is +expected to send the mail into another Postfix SMTP server process +for normal delivery. See the SMTPD_PROXY_README file for details. + +Improved LDAP client robustness. Given suitable invalid database +contents, LDAP lookups can produce too many results, enter an +infinite loop in the expansion of "special result attributes" (LDAP +DNs and LDAP URLs) or can simply consume excessive server resources. +Credits to Victor Duchovni and Lamont Jones. + +New CIDR-based lookup table, remotely based on code by Jozsef +Kadlecsik. For details and examples, see "man cidr_table". + +The TCP-based client-server table lookup protocol is finished. +For details and examples, see "man tcp_table". This will allow you +to implement your own greylisting. + +Support for !/pattern/ (negative matches) in PCRE lookup tables by +Victor Duchovni. See "man pcre_table" for more. + +New enable_original_recipient parameter (default: yes) to control +whether Postfix keeps track of original recipient address information. +If this is turned off Postfix produces no X-Original-To: headers +and ignores the original recipient when eliminating duplicates +after virtual alias expansion. Code by Victor Duchovni. + +Finer control over how long the SMTP server waits for address +verification probes to complete. address_verify_poll_{count,delay} +control how often to query the verify server and how long to wait +between queries. Specify address_verify_poll_count=1 to implement +a crude form of greylisting. + Major changes with Postfix snapshot 2.0.11-20030611 =================================================== diff --git a/postfix/conf/access b/postfix/conf/access index 1f4b38ba7..baacf9c42 100644 --- a/postfix/conf/access +++ b/postfix/conf/access @@ -108,72 +108,75 @@ # A network address is a sequence of one or more # octets separated by ".". # +# NOTE: use the cidr lookup table type if you want to +# specify arbitrary network blocks. +# # ACTIONS # [45]NN text -# Reject the address etc. that matches the pattern, +# Reject the address etc. that matches the pattern, # and respond with the numerical code and text. # # REJECT # # REJECT optional text... -# Reject the address etc. that matches the pattern. -# Reply with $reject_code optional text... when the -# optional text is specified, otherwise reply with a +# Reject the address etc. that matches the pattern. +# Reply with $reject_code optional text... when the +# optional text is specified, otherwise reply with a # generic error response message. # # OK Accept the address etc. that matches the pattern. # # all-numerical # An all-numerical result is treated as OK. This for- -# mat is generated by address-based relay authoriza- +# mat is generated by address-based relay authoriza- # tion schemes. # -# DUNNO Pretend that the lookup key was not found in this +# DUNNO Pretend that the lookup key was not found in this # table. This prevents Postfix from trying substrings -# of the lookup key (such as a subdomain name, or a +# of the lookup key (such as a subdomain name, or a # network address subnetwork). # # HOLD # # HOLD optional text... -# Place the message on the hold queue, where it will -# sit until someone either deletes it or releases it -# for delivery. Log the optional text if specified, +# Place the message on the hold queue, where it will +# sit until someone either deletes it or releases it +# for delivery. Log the optional text if specified, # otherwise log a generic message. # -# Mail that is placed on hold can be examined with -# the postcat(1) command, and can be destroyed or +# Mail that is placed on hold can be examined with +# the postcat(1) command, and can be destroyed or # released with the postsuper(1) command. # -# Note: this action currently affects all recipients +# Note: this action currently affects all recipients # of the message. # # DISCARD # # DISCARD optional text... -# Claim successful delivery and silently discard the -# message. Log the optional text if specified, oth- +# Claim successful delivery and silently discard the +# message. Log the optional text if specified, oth- # erwise log a generic message. # -# Note: this action currently affects all recipients +# Note: this action currently affects all recipients # of the message. # # FILTER transport:destination -# After the message is queued, send the entire mes- -# sage through a content filter. More information +# After the message is queued, send the entire mes- +# sage through a content filter. More information # about content filters is in the Postfix FIL- # TER_README file. # -# Note: this action overrides the main.cf con- +# Note: this action overrides the main.cf con- # tent_filter setting, and currently affects all # recipients of the message. # # REDIRECT user@domain -# After the message is queued, send the message to +# After the message is queued, send the message to # the specified address instead of the intended # recipient(s). # -# Note: this action overrides the FILTER action, and +# Note: this action overrides the FILTER action, and # currently affects all recipients of the message. # # restriction... @@ -181,39 +184,40 @@ # reject_unauth_destination, and so on). # # REGULAR EXPRESSION TABLES -# This section describes how the table lookups change when +# This section describes how the table lookups change when # the table is given in the form of regular expressions. For -# a description of regular expression lookup table syntax, +# a description of regular expression lookup table syntax, # see regexp_table(5) or pcre_table(5). # -# Each pattern is a regular expression that is applied to +# Each pattern is a regular expression that is applied to # the entire string being looked up. Depending on the appli- -# cation, that string is an entire client hostname, an +# cation, that string is an entire client hostname, an # entire client IP address, or an entire mail address. Thus, # no parent domain or parent network search is done, -# user@domain mail addresses are not broken up into their +# user@domain mail addresses are not broken up into their # user@ and domain constituent parts, nor is user+foo broken # up into user and foo. # -# Patterns are applied in the order as specified in the -# table, until a pattern is found that matches the search +# Patterns are applied in the order as specified in the +# table, until a pattern is found that matches the search # string. # -# Actions are the same as with indexed file lookups, with -# the additional feature that parenthesized substrings from +# Actions are the same as with indexed file lookups, with +# the additional feature that parenthesized substrings from # the pattern can be interpolated as $1, $2 and so on. # # BUGS -# The table format does not understand quoting conventions. +# The table format does not understand quoting conventions. # # SEE ALSO -# postmap(1) create mapping table +# postmap(1) create lookup table # smtpd(8) smtp server +# cidr_table(5) format of CIDR tables # pcre_table(5) format of PCRE tables # regexp_table(5) format of POSIX regular expression tables # # 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/conf/cidr_table b/postfix/conf/cidr_table new file mode 100644 index 000000000..76b27d484 --- /dev/null +++ b/postfix/conf/cidr_table @@ -0,0 +1,77 @@ +# CIDR_TABLE(5) CIDR_TABLE(5) +# +# NAME +# cidr_table - format of Postfix CIDR tables +# +# SYNOPSIS +# postmap -q "string" cidr:/etc/postfix/filename +# +# postmap -q - cidr:/etc/postfix/filename if you want +# to use the null sender address. Beware, some sites reject mail from +# <>, even though RFCs require that such addresses to be accepted. +# +#address_verify_sender = <> +#address_verify_sender = postmaster@my.domain +address_verify_sender = postmaster + +# The address_verify_poll_count parameter specifies how many times +# to query the address verification service for completion of an +# address verification request. Specify 0 to implement a simple form +# of greylisting, that is, always defer the first delivery request +# from an unknown sender address. +# +#address_verify_poll_count = 0 +address_verify_poll_count = 3 + +# The address_verify_poll_delay parameter specifies how long to wait +# after querying the address verification service for completion of +# an address verification request. +# +address_verify_poll_delay = 3 + +# +# CACHE CONTROL +# # The address_verify_map configuration parameter specifies an optional -# table for persistent recipient status storage. The file is opened +# table for persistent address status storage. The file is opened # before the process enters a chroot jail and before it drops root # privileges. # @@ -24,20 +56,6 @@ #address_verify_map = btree:/etc/postfix/verify address_verify_map = -# The address_verify_sender configuration parameter specifies the -# sender address that Postfix will use in address verification probe -# messages. -# -# By default, the probe sender address is postmaster@$myorigin. -# -# Specify an empty value (address_verify_sender =) or <> if you want -# to use the null sender address. Beware, some sites reject mail from -# <>, even though RFCs require that such addresses to be accepted. -# -#address_verify_sender = <> -#address_verify_sender = postmaster@my.domain -address_verify_sender = postmaster - # The address_verify_positive_expire_time configuration parameter # specifies the amount of time after which a known to be good address # expires. diff --git a/postfix/conf/tcp_table b/postfix/conf/tcp_table new file mode 100644 index 000000000..db1b38503 --- /dev/null +++ b/postfix/conf/tcp_table @@ -0,0 +1,85 @@ +# TCP_TABLE(5) TCP_TABLE(5) +# +# NAME +# tcp_table - Postfix client/server table lookup protocol +# +# SYNOPSIS +# postmap -q "string" tcp:host:port +# +# postmap -q - regexp:host:port cidr lookup table type if you want to + specify arbitrary network blocks. + ACTIONS [45]NN text - Reject the address etc. that matches the pattern, + Reject the address etc. that matches the pattern, and respond with the numerical code and text. REJECT REJECT optional text... - Reject the address etc. that matches the pattern. - Reply with $reject_code optional text... when the - optional text is specified, otherwise reply with a + Reject the address etc. that matches the pattern. + Reply with $reject_code optional text... when the + optional text is specified, otherwise reply with a generic error response message. OK Accept the address etc. that matches the pattern. all-numerical An all-numerical result is treated as OK. This for- - mat is generated by address-based relay authoriza- + mat is generated by address-based relay authoriza- tion schemes. - DUNNO Pretend that the lookup key was not found in this + DUNNO Pretend that the lookup key was not found in this table. This prevents Postfix from trying substrings - of the lookup key (such as a subdomain name, or a + of the lookup key (such as a subdomain name, or a network address subnetwork). HOLD HOLD optional text... - Place the message on the hold queue, where it will - sit until someone either deletes it or releases it - for delivery. Log the optional text if specified, + Place the message on the hold queue, where it will + sit until someone either deletes it or releases it + for delivery. Log the optional text if specified, otherwise log a generic message. - Mail that is placed on hold can be examined with - the postcat(1) command, and can be destroyed or + Mail that is placed on hold can be examined with + the postcat(1) command, and can be destroyed or released with the postsuper(1) command. - Note: this action currently affects all recipients + Note: this action currently affects all recipients of the message. DISCARD DISCARD optional text... - Claim successful delivery and silently discard the - message. Log the optional text if specified, oth- + Claim successful delivery and silently discard the + message. Log the optional text if specified, oth- erwise log a generic message. - Note: this action currently affects all recipients + Note: this action currently affects all recipients of the message. FILTER transport:destination - After the message is queued, send the entire mes- - sage through a content filter. More information + After the message is queued, send the entire mes- + sage through a content filter. More information about content filters is in the Postfix FIL- TER_README file. - Note: this action overrides the main.cf con- + Note: this action overrides the main.cf con- tent_filter setting, and currently affects all recipients of the message. REDIRECT user@domain - After the message is queued, send the message to + After the message is queued, send the message to the specified address instead of the intended recipient(s). - Note: this action overrides the FILTER action, and + Note: this action overrides the FILTER action, and currently affects all recipients of the message. restriction... @@ -182,39 +185,40 @@ ACCESS(5) ACCESS(5) reject_unauth_destination, and so on). REGULAR EXPRESSION TABLES - This section describes how the table lookups change when + This section describes how the table lookups change when the table is given in the form of regular expressions. For - a description of regular expression lookup table syntax, + a description of regular expression lookup table syntax, see regexp_table(5) or pcre_table(5). - Each pattern is a regular expression that is applied to + Each pattern is a regular expression that is applied to the entire string being looked up. Depending on the appli- - cation, that string is an entire client hostname, an + cation, that string is an entire client hostname, an entire client IP address, or an entire mail address. Thus, no parent domain or parent network search is done, - user@domain mail addresses are not broken up into their + user@domain mail addresses are not broken up into their user@ and domain constituent parts, nor is user+foo broken up into user and foo. - Patterns are applied in the order as specified in the - table, until a pattern is found that matches the search + Patterns are applied in the order as specified in the + table, until a pattern is found that matches the search string. - Actions are the same as with indexed file lookups, with - the additional feature that parenthesized substrings from + Actions are the same as with indexed file lookups, with + the additional feature that parenthesized substrings from the pattern can be interpolated as $1, $2 and so on. BUGS - The table format does not understand quoting conventions. + The table format does not understand quoting conventions. SEE ALSO - postmap(1) create mapping table + postmap(1) create lookup table smtpd(8) smtp server + cidr_table(5) format of CIDR tables pcre_table(5) format of PCRE tables regexp_table(5) format of POSIX regular expression tables 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 d6a5eecda..50266a5a4 100644 --- a/postfix/html/cleanup.8.html +++ b/postfix/html/cleanup.8.html @@ -171,6 +171,14 @@ CLEANUP(8) CLEANUP(8) Address mapping lookup table for sender and recipi- ent addresses in envelopes and headers. + enable_original_recipient + Enable support for the X-Original-To message + header, which is needed for multi-recipient mail- + boxes. When this is enabled, Postfix performs + duplicate elimination on (original recipient, + rewritten recipient) pairs, instead of looking at + the rewritten recipient only. + recipient_canonical_maps Address mapping lookup table for envelope and header recipient addresses. @@ -180,16 +188,16 @@ CLEANUP(8) CLEANUP(8) header sender addresses. masquerade_classes - List of address classes subject to masquerading: - zero or more of envelope_sender, envelope_recipi- + List of address classes subject to masquerading: + zero or more of envelope_sender, envelope_recipi- ent, header_sender, header_recipient. masquerade_domains - List of domains that hide their subdomain struc- + List of domains that hide their subdomain struc- ture. masquerade_exceptions - List of user names that are not subject to address + List of user names that are not subject to address masquerading. virtual_alias_maps @@ -198,7 +206,7 @@ CLEANUP(8) CLEANUP(8) Resource controls duplicate_filter_limit - Limits the number of envelope recipients that are + Limits the number of envelope recipients that are remembered. header_address_token_limit @@ -206,21 +214,21 @@ CLEANUP(8) CLEANUP(8) a message header. header_size_limit - Limits the amount of memory in bytes used to pro- + Limits the amount of memory in bytes used to pro- cess a message header. in_flow_delay Amount of time to pause before accepting a message, - when the message arrival rate exceeds the message + when the message arrival rate exceeds the message delivery rate. virtual_alias_expansion_limit - Limit the number of actual recipients produced by - virtual alias expansion from each original recipi- + Limit the number of actual recipients produced by + virtual alias expansion from each original recipi- ent. virtual_alias_recursion_limit - Limit the recursion depth of virtual alias expan- + Limit the recursion depth of virtual alias expan- sion. SEE ALSO @@ -235,7 +243,7 @@ CLEANUP(8) CLEANUP(8) /etc/postfix/virtual*, virtual mapping table 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/faq.html b/postfix/html/faq.html index dba261f2a..3a81f15ce 100644 --- a/postfix/html/faq.html +++ b/postfix/html/faq.html @@ -2618,8 +2618,9 @@ the virtual_mailbox_maps parameter.

-If you want to deliver the domain as a Postfix simulated virtual(5) domain, then you should list +If you want to deliver the domain as a +virtual(5) alias domain, where each address is aliased to +a real local or remote address, then you should list the virtual domain name in the tables specified with the virtual_alias_domains parameter instead. @@ -2638,7 +2639,7 @@ Solutions:

    -
  • Specify a simulated virtual domain as per the +
  • Specify a virtual alias domain as per the virtual(5) manual page.

    @@ -2715,6 +2716,15 @@ that the mail was sent to. Answer: Postfix logs the original recipient address in the X-Original-To: message header. +

    + +This requires that the enable_original_recipient parameter +is not changed from its default value of yes. With +enable_original_recipient set to no, messages to +multiple recipients in the domain will only be delivered to +the first recipient, and the X-Original-To: header will +not be added to the message. +


    Address masquerading with exceptions

    diff --git a/postfix/html/pcre_table.5.html b/postfix/html/pcre_table.5.html index 6be1fd8c7..629ab56e5 100644 --- a/postfix/html/pcre_table.5.html +++ b/postfix/html/pcre_table.5.html @@ -5,8 +5,6 @@ PCRE_TABLE(5) PCRE_TABLE(5) pcre_table - format of Postfix PCRE tables SYNOPSIS - pcre:/etc/postfix/filename - postmap -q "string" pcre:/etc/postfix/filename postmap -q - pcre:/etc/postfix/filename <inputfile @@ -26,8 +24,10 @@ PCRE_TABLE(5) PCRE_TABLE(5) The general form of a PCRE table is: /pattern/flags result - When pattern matches a search string, use the cor- - responding result value. + + !/pattern/flags result + When pattern matches (does not match) a search + string, use the corresponding result value. blank lines and comments Empty lines and whitespace-only lines are ignored, @@ -41,98 +41,104 @@ PCRE_TABLE(5) PCRE_TABLE(5) if /pattern/flags + if !/pattern/flags + endif Examine the lines between if..endif only if pattern - matches. The if..endif can nest. Do not prepend - whitespace to patterns inside if..endif. + matches (does not match). The if..endif can nest. + Do not prepend whitespace to patterns inside + if..endif. Each pattern is a perl-like regular expression. The - expression delimiter can be any character, except whites- - pace or characters that have special meaning (tradition- - ally the forward slash is used). The regular expression + expression delimiter can be any character, except whites- + pace or characters that have special meaning (tradition- + ally the forward slash is used). The regular expression can contain whitespace. By default, matching is case-insensitive, and newlines are - not treated as special characters. The behavior is con- - trolled by flags, which are toggled by appending one or + not treated as special characters. The behavior is con- + trolled by flags, which are toggled by appending one or more of the following characters after the pattern: i (default: on) - Toggles the case sensitivity flag. By default, + Toggles the case sensitivity flag. By default, matching is case insensitive. m (default: off) - Toggles the PCRE_MULTILINE flag. When this flag is - on, the ^ and $ metacharacters match immediately - after and immediately before a newline character, - respectively, in addition to matching at the start + Toggles the PCRE_MULTILINE flag. When this flag is + on, the ^ and $ metacharacters match immediately + after and immediately before a newline character, + respectively, in addition to matching at the start and end of the subject string. s (default: on) Toggles the PCRE_DOTALL flag. When this flag is on, the . metacharacter matches the newline character. - With Postfix versions prior to 20020528, The flag + With Postfix versions prior to 20020528, The flag is off by default, which is inconvenient for multi- line message header matching. x (default: off) - Toggles the pcre extended flag. When this flag is - on, whitespace in the pattern (other than in a + Toggles the pcre extended flag. When this flag is + on, whitespace in the pattern (other than in a character class) and characters between a # outside - a character class and the next newline character - are ignored. An escaping backslash can be used to - include a whitespace or # character as part of the + a character class and the next newline character + are ignored. An escaping backslash can be used to + include a whitespace or # character as part of the pattern. A (default: off) - Toggles the PCRE_ANCHORED flag. When this flag is - on, the pattern is forced to be "anchored", that + Toggles the PCRE_ANCHORED flag. When this flag is + on, the pattern is forced to be "anchored", that is, it is constrained to match only at the start of - the string which is being searched (the "subject - string"). This effect can also be achieved by + the string which is being searched (the "subject + string"). This effect can also be achieved by appropriate constructs in the pattern itself. E (default: off) - Toggles the PCRE_DOLLAR_ENDONLY flag. When this - flag is on, a $ metacharacter in the pattern - matches only at the end of the subject string. - Without this flag, a dollar also matches immedi- + Toggles the PCRE_DOLLAR_ENDONLY flag. When this + flag is on, a $ metacharacter in the pattern + matches only at the end of the subject string. + Without this flag, a dollar also matches immedi- ately before the final character if it is a newline character (but not before any other newline charac- - ters). This flag is ignored if PCRE_MULTILINE flag + ters). This flag is ignored if PCRE_MULTILINE flag is set. U (default: off) Toggles the ungreedy matching flag. When this flag - is on, the pattern matching engine inverts the - "greediness" of the quantifiers so that they are - not greedy by default, but become greedy if fol- - lowed by "?". This flag can also set by a (?U) + is on, the pattern matching engine inverts the + "greediness" of the quantifiers so that they are + not greedy by default, but become greedy if fol- + lowed by "?". This flag can also set by a (?U) modifier within the pattern. X (default: off) Toggles the PCRE_EXTRA flag. When this flag is on, - any backslash in a pattern that is followed by a + any backslash in a pattern that is followed by a letter that has no special meaning causes an error, thus reserving these combinations for future expan- sion. - Each pattern is applied to the entire lookup key string. - Depending on the application, that string is an entire + Each pattern is applied to the entire lookup key string. + Depending on the application, that string is an entire client hostname, an entire client IP address, or an entire - mail address. Thus, no parent domain or parent network - search is done, and user@domain mail addresses are not - broken up into their user and domain constituent parts, + mail address. Thus, no parent domain or parent network + search is done, and user@domain mail addresses are not + broken up into their user and domain constituent parts, nor is user+foo broken up into user and foo. - Patterns are applied in the order as specified in the - table, until a pattern is found that matches the search + Patterns are applied in the order as specified in the + table, until a pattern is found that matches the search string. - Substitution of substrings from the matched expression - into the result string is possible using the conventional - perl syntax ($1, $2, etc.). The macros in the result - string may need to be written as ${n} or $(n) if they - aren't followed by whitespace. + Substitution of substrings from the matched expression + into the result string is possible using the conventional + perl syntax ($1, $2, etc.). The macros in the result + string may need to be written as ${n} or $(n) if they + aren't followed by whitespace. Since negated patterns + (those preceded by !) return a result when the expression + does not match, substitutions are not available for + negated patterns. EXAMPLE SMTPD ACCESS MAP # Protect your outgoing majordomo exploders @@ -161,6 +167,8 @@ PCRE_TABLE(5) PCRE_TABLE(5) SEE ALSO regexp_table(5) format of POSIX regular expression tables + cidr_table(5) format of CIDR tables + tcp_table(5) TCP client/server table lookup protocol AUTHOR(S) The PCRE table lookup code was originally written by: diff --git a/postfix/html/regexp_table.5.html b/postfix/html/regexp_table.5.html index 624820adb..60e8097ea 100644 --- a/postfix/html/regexp_table.5.html +++ b/postfix/html/regexp_table.5.html @@ -5,8 +5,6 @@ REGEXP_TABLE(5) REGEXP_TABLE(5) regexp_table - format of Postfix regular expression tables SYNOPSIS - regexp:/etc/postfix/filename - postmap -q "string" regexp:/etc/postfix/filename postmap -q - regexp:/etc/postfix/filename <inputfile @@ -78,7 +76,10 @@ REGEXP_TABLE(5) REGEXP_TABLE(5) Substitution of substrings from the matched expression into the result string is possible using $1, $2, etc.. The macros in the result string may need to be written as ${n} - or $(n) if they aren't followed by whitespace. + or $(n) if they aren't followed by whitespace. Since + negated patterns (those preceded by !) return a result + when the expression does not match, substitutions are not + available for negated patterns. EXAMPLE SMTPD ACCESS MAP # Disallow sender-specified routing. This is a must if you relay mail @@ -107,6 +108,8 @@ REGEXP_TABLE(5) REGEXP_TABLE(5) SEE ALSO pcre_table(5) format of PCRE tables + cidr_table(5) format of CIDR tables + tcp_table(5) TCP client/server table lookup protocol AUTHOR(S) The regexp table lookup code was originally written by: diff --git a/postfix/html/rewrite.html b/postfix/html/rewrite.html index a432b0194..87d8b7444 100644 --- a/postfix/html/rewrite.html +++ b/postfix/html/rewrite.html @@ -318,8 +318,8 @@ href="cleanup.8.html">cleanup daemon uses the virtual alias table to redirect mail for all recipients, local or remote. The mapping affects only envelope recipients; it has no effect on message headers or envelope senders. -Virtual alias lookups are useful to redirect mail for simulated -virtual domains to real user mailboxes, and to redirect mail for +Virtual alias lookups are useful to redirect mail for virtual +alias domains to real user mailboxes, and to redirect mail for domains that no longer exist. Virtual alias lookups can also be used to transform Firstname.Lastname back into UNIX login names, although it seems that local aliases diff --git a/postfix/html/smtp.8.html b/postfix/html/smtp.8.html index be7b18649..167e4b343 100644 --- a/postfix/html/smtp.8.html +++ b/postfix/html/smtp.8.html @@ -25,11 +25,6 @@ SMTP(8) SMTP(8) preference, and connects to each listed address until it finds a server that responds. - When the domain or host is specified as a comma/whitespace - separated list, the SMTP client repeats the above process - for all destinations until it finds a server that - responds. - Once the SMTP client has received the server greeting ban- ner, no error will cause it to proceed to the next address on the mail exchanger list. Instead, the message is either @@ -37,7 +32,7 @@ SMTP(8) SMTP(8) SECURITY The SMTP client is moderately security-sensitive. It talks - to SMTP servers and to DNS servers on the network. The + to SMTP servers and to DNS servers on the network. The SMTP client can be run chrooted at fixed low privilege. STANDARDS @@ -53,80 +48,80 @@ SMTP(8) SMTP(8) RFC 2920 (SMTP Pipelining) DIAGNOSTICS - Problems and transactions are logged to syslogd(8). Cor- - rupted message files are marked so that the queue manager + Problems and transactions are logged to syslogd(8). Cor- + rupted message files are marked so that the queue manager can move them to the corrupt queue for further inspection. - Depending on the setting of the notify_classes parameter, - the postmaster is notified of bounces, protocol problems, + Depending on the setting of the notify_classes parameter, + the postmaster is notified of bounces, protocol problems, and of other trouble. BUGS CONFIGURATION PARAMETERS - The following main.cf parameters are especially relevant - to this program. See the Postfix main.cf file for syntax - details and for default values. Use the postfix reload + The following main.cf parameters are especially relevant + to this program. See the Postfix main.cf file for syntax + details and for default values. Use the postfix reload command after a configuration change. Miscellaneous best_mx_transport - Name of the delivery transport to use when the - local machine is the most-preferred mail exchanger - (by default, a mailer loop is reported, and the + Name of the delivery transport to use when the + local machine is the most-preferred mail exchanger + (by default, a mailer loop is reported, and the message is bounced). debug_peer_level - Verbose logging level increment for hosts that + Verbose logging level increment for hosts that match a pattern in the debug_peer_list parameter. debug_peer_list - List of domain or network patterns. When a remote - host matches a pattern, increase the verbose log- - ging level by the amount specified in the + List of domain or network patterns. When a remote + host matches a pattern, increase the verbose log- + ging level by the amount specified in the debug_peer_level parameter. disable_dns_lookups - Disable DNS lookups. This means that mail must be + Disable DNS lookups. This means that mail must be forwarded via a smart relay host. smtp_host_lookup - What host lookup mechanism the SMTP client should - use. Specify dns (use DNS lookup) and/or native - (use the native naming service which also uses - /etc/hosts). This setting is ignored when DNS + What host lookup mechanism the SMTP client should + use. Specify dns (use DNS lookup) and/or native + (use the native naming service which also uses + /etc/hosts). This setting is ignored when DNS lookups are disabled. error_notice_recipient - Recipient of protocol/policy/resource/software + Recipient of protocol/policy/resource/software error notices. fallback_relay - Hosts to hand off mail to if a message destination + Hosts to hand off mail to if a message destination is not found or if a destination is unreachable. ignore_mx_lookup_error When a name server fails to respond to an MX query, - search for an A record instead deferring mail + search for an A record instead deferring mail delivery. inet_interfaces The network interface addresses that this mail sys- - tem receives mail on. When any of those addresses + tem receives mail on. When any of those addresses appears in the list of mail exchangers for a remote - destination, the list is truncated to avoid mail + destination, the list is truncated to avoid mail delivery loops. See also the proxy_interfaces parameter. notify_classes - When this parameter includes the protocol class, - send mail to the postmaster with transcripts of + When this parameter includes the protocol class, + send mail to the postmaster with transcripts of SMTP sessions with protocol errors. proxy_interfaces - Network interfaces that this mail system receives + Network interfaces that this mail system receives mail on by way of a proxy or network address trans- - lator. When any of those addresses appears in the - list of mail exchangers for a remote destination, + lator. When any of those addresses appears in the + list of mail exchangers for a remote destination, the list is truncated to avoid mail delivery loops. See also the inet_interfaces parameter. @@ -137,16 +132,20 @@ SMTP(8) SMTP(8) Never send EHLO at the start of a connection. smtp_bind_address - Numerical source network address to bind to when + Numerical source network address to bind to when making a connection. smtp_line_length_limit - Length limit for SMTP message content lines. Zero - means no limit. Some SMTP servers misbehave on + Length limit for SMTP message content lines. Zero + means no limit. Some SMTP servers misbehave on long lines. smtp_helo_name - The hostname to be used in HELO and EHLO commands. + The hostname to be used in HELO and EHLO commands. + + smtp_quote_rfc821_envelope + Whether or not to quote MAIL FROM and RCPT TO + addresses as per the rules laid out in RFC 821. smtp_skip_4xx_greeting Skip servers that greet us with a 4xx status code. diff --git a/postfix/html/smtpd.8.html b/postfix/html/smtpd.8.html index 4aca9e81b..9e98fba74 100644 --- a/postfix/html/smtpd.8.html +++ b/postfix/html/smtpd.8.html @@ -114,29 +114,48 @@ SMTPD(8) SMTPD(8) reject_sender_login_mismatch sender anti-spoofing restriction. +Pass-through proxy + Optionally, the Postfix SMTP server can be configured to + forward all mail to a proxy server, for example a real- + time content filter. This proxy server should support the + same MAIL FROM and RCPT TO command syntax as Postfix, but + does not need to support ESMTP command pipelining. + + smtpd_proxy_filter + The host:port of the SMTP proxy server. The host or + host: portion is optional. + + smtpd_proxy_timeout + Timeout for connecting to, sending to and receiving + from the SMTP proxy server. + + smtpd_proxy_ehlo + The hostname to use when sending an EHLO command to + the SMTP proxy server. + Miscellaneous authorized_verp_clients Hostnames, domain names and/or addresses of clients that are authorized to use the XVERP extension. debug_peer_level - Increment in verbose logging level when a remote + Increment in verbose logging level when a remote host matches a pattern in the debug_peer_list parameter. debug_peer_list - List of domain or network patterns. When a remote - host matches a pattern, increase the verbose log- - ging level by the amount specified in the + List of domain or network patterns. When a remote + host matches a pattern, increase the verbose log- + ging level by the amount specified in the debug_peer_level parameter. default_verp_delimiters The default VERP delimiter characters that are used - when the XVERP command is specified without + when the XVERP command is specified without explicit delimiters. error_notice_recipient - Recipient of protocol/policy/resource/software + Recipient of protocol/policy/resource/software error notices. hopcount_limit @@ -145,18 +164,18 @@ SMTPD(8) SMTPD(8) notify_classes List of error classes. Of special interest are: - policy When a client violates any policy, mail a + policy When a client violates any policy, mail a transcript of the entire SMTP session to the postmaster. protocol - When a client violates the SMTP protocol or + When a client violates the SMTP protocol or issues an unimplemented command, mail a transcript of the entire SMTP session to the postmaster. smtpd_banner - Text that follows the 220 status code in the SMTP + Text that follows the 220 status code in the SMTP greeting banner. smtpd_expansion_filter @@ -164,57 +183,57 @@ SMTPD(8) SMTPD(8) expansion of rbl template responses and other text. smtpd_recipient_limit - Restrict the number of recipients that the SMTP + Restrict the number of recipients that the SMTP server accepts per message delivery. smtpd_timeout - Limit the time to send a server response and to + Limit the time to send a server response and to receive a client request. soft_bounce - Change hard (5xx) reject responses into soft (4xx) - reject responses. This can be useful for testing + Change hard (5xx) reject responses into soft (4xx) + reject responses. This can be useful for testing purposes. verp_delimiter_filter - The characters that Postfix accepts as VERP delim- + The characters that Postfix accepts as VERP delim- iter characters. Known versus unknown recipients show_user_unknown_table_name - Whether or not to reveal the table name in the - "User unknown" responses. The extra detail makes - trouble shooting easier but also reveals informa- + Whether or not to reveal the table name in the + "User unknown" responses. The extra detail makes + trouble shooting easier but also reveals informa- tion that is nobody elses business. unknown_local_recipient_reject_code The response code when a client specifies a recipi- - ent whose domain matches $mydestination or + ent whose domain matches $mydestination or $inet_interfaces, while $local_recipient_maps is - non-empty and does not list the recipient address + non-empty and does not list the recipient address or address local-part. unknown_relay_recipient_reject_code The response code when a client specifies a recipi- ent whose domain matches $relay_domains, while - $relay_recipient_maps is non-empty and does not + $relay_recipient_maps is non-empty and does not list the recipient address. unknown_virtual_alias_reject_code The response code when a client specifies a recipi- - ent whose domain matches $virtual_alias_domains, - while the recipient is not listed in $vir- + ent whose domain matches $virtual_alias_domains, + while the recipient is not listed in $vir- tual_alias_maps. unknown_virtual_mailbox_reject_code The response code when a client specifies a recipi- - ent whose domain matches $virtual_mailbox_domains, + ent whose domain matches $virtual_mailbox_domains, while the recipient is not listed in $virtual_mail- box_maps. Resource controls line_length_limit - Limit the amount of memory in bytes used for the + Limit the amount of memory in bytes used for the handling of partial input lines. message_size_limit @@ -222,8 +241,8 @@ SMTPD(8) SMTPD(8) ing on-disk storage for envelope information. queue_minfree - Minimal amount of free space in bytes in the queue - file system for the SMTP server to accept any mail + Minimal amount of free space in bytes in the queue + file system for the SMTP server to accept any mail at all. smtpd_history_flush_threshold @@ -238,23 +257,23 @@ SMTPD(8) SMTPD(8) smtpd_soft_error_limit When an SMTP client has made this number of errors, - wait error_count seconds before responding to any + wait error_count seconds before responding to any client request. smtpd_hard_error_limit - Disconnect after a client has made this number of + Disconnect after a client has made this number of errors. smtpd_junk_command_limit Limit the number of times a client can issue a junk - command such as NOOP, VRFY, ETRN or RSET in one - SMTP session before it is penalized with tarpit + command such as NOOP, VRFY, ETRN or RSET in one + SMTP session before it is penalized with tarpit delays. UCE control restrictions parent_domain_matches_subdomains - List of Postfix features that use domain.tld pat- - terns to match sub.domain.tld (as opposed to + List of Postfix features that use domain.tld pat- + terns to match sub.domain.tld (as opposed to requiring .domain.tld patterns). smtpd_client_restrictions @@ -262,19 +281,19 @@ SMTPD(8) SMTPD(8) tem. smtpd_helo_required - Require that clients introduce themselves at the + Require that clients introduce themselves at the beginning of an SMTP session. smtpd_helo_restrictions - Restrict what client hostnames are allowed in HELO + Restrict what client hostnames are allowed in HELO and EHLO commands. smtpd_sender_restrictions - Restrict what sender addresses are allowed in MAIL + Restrict what sender addresses are allowed in MAIL FROM commands. smtpd_recipient_restrictions - Restrict what recipient addresses are allowed in + Restrict what recipient addresses are allowed in RCPT TO commands. smtpd_etrn_restrictions @@ -282,42 +301,60 @@ SMTPD(8) SMTPD(8) mands, and what clients may issue ETRN commands. smtpd_data_restrictions - Restrictions on the DATA command. Currently, the - only restriction that makes sense here is + Restrictions on the DATA command. Currently, the + only restriction that makes sense here is reject_unauth_pipelining. allow_untrusted_routing - Allow untrusted clients to specify addresses with - sender-specified routing. Enabling this opens up - nasty relay loopholes involving trusted backup MX + Allow untrusted clients to specify addresses with + sender-specified routing. Enabling this opens up + nasty relay loopholes involving trusted backup MX hosts. smtpd_restriction_classes - Declares the name of zero or more parameters that - contain a list of UCE restrictions. The names of - these parameters can then be used instead of the + Declares the name of zero or more parameters that + contain a list of UCE restrictions. The names of + these parameters can then be used instead of the restriction lists that they represent. smtpd_null_access_lookup_key - The lookup key to be used in SMTPD access tables - instead of the null sender address. A null sender + The lookup key to be used in SMTPD access tables + instead of the null sender address. A null sender address cannot be looked up. maps_rbl_domains (deprecated) - List of DNS domains that publish the addresses of + List of DNS domains that publish the addresses of blacklisted hosts. This is used with the deprecated reject_maps_rbl restriction. permit_mx_backup_networks - Only domains whose primary MX hosts match the - listed networks are eligible for the per- + Only domains whose primary MX hosts match the + listed networks are eligible for the per- mit_mx_backup feature. relay_domains - Restrict what domains this mail system will relay - mail to. The domains are routed to the delivery + Restrict what domains this mail system will relay + mail to. The domains are routed to the delivery agent specified with the relay_transport setting. +Sender/recipient address verification + Address verification is implemented by sending probe email + messages that are not actually delivered, and is enabled + via the reject_unverified_{sender,recipient} access + restriction. The status of verification probes is main- + tained by the address verification service. + + address_verify_poll_count + How many times to query the address verification + service for completion of an address verification + request. Specify 0 to implement a simple form of + greylisting. + + address_verify_poll_delay + Time to wait after querying the address verifica- + tion service for completion of an address verifica- + tion request. + UCE control responses access_map_reject_code Response code when a client violates an access @@ -381,10 +418,11 @@ SMTPD(8) SMTPD(8) be undeliverable. SEE ALSO - trivial-rewrite(8) address resolver cleanup(8) message canonicalization master(8) process manager syslogd(8) system logging + trivial-rewrite(8) address resolver + verify(8) address verification service LICENSE The Secure Mailer license must be distributed with this diff --git a/postfix/html/transport.5.html b/postfix/html/transport.5.html index 3d63496b8..4d6185c8f 100644 --- a/postfix/html/transport.5.html +++ b/postfix/html/transport.5.html @@ -17,21 +17,40 @@ TRANSPORT(5) TRANSPORT(5) relay hosts. The mapping is used by the trivial-rewrite(8) daemon. - Normally, the transport table is specified as a text file - that serves as input to the postmap(1) command. The - result, an indexed file in dbm or db format, is used for - fast searching by the mail system. Execute the command - postmap /etc/postfix/transport in order to rebuild the + This mapping overrides the default routing that is built + into Postfix: + + mydestination + A list of domains that is by default delivered via + $local_transport. + + virtual_mailbox_domains + A list of domains that is by default delivered via + $virtual_transport. + + relay_domains + A list of domains that is by default delivered via + $relay_transport. + + any other destination + Mail for any other destination is by default deliv- + ered via $default_transport. + + Normally, the transport table is specified as a text file + that serves as input to the postmap(1) command. The + result, an indexed file in dbm or db format, is used for + fast searching by the mail system. Execute the command + postmap /etc/postfix/transport in order to rebuild the indexed file after changing the transport table. - When the table is provided via other means such as NIS, - LDAP or SQL, the same lookups are done as for ordinary + When the table is provided via other means such as NIS, + LDAP or SQL, the same lookups are done as for ordinary indexed files. - Alternatively, the table can be provided as a regular- + Alternatively, the table can be provided as a regular- expression map where patterns are given as regular expres- - sions. In that case, the lookups are done in a slightly - different way as described in section "REGULAR EXPRESSION + sions. In that case, the lookups are done in a slightly + different way as described in section "REGULAR EXPRESSION TABLES". TABLE FORMAT @@ -42,28 +61,28 @@ TRANSPORT(5) TRANSPORT(5) domain, use the corresponding result. blank lines and comments - Empty lines and whitespace-only lines are ignored, - as are lines whose first non-whitespace character + Empty lines and whitespace-only lines are ignored, + as are lines whose first non-whitespace character is a `#'. multi-line text - A logical line starts with non-whitespace text. A - line that starts with whitespace continues a logi- + A logical line starts with non-whitespace text. A + line that starts with whitespace continues a logi- cal line. - The pattern specifies an email address, a domain name, or - a domain name hierarchy, as described in section "TABLE + The pattern specifies an email address, a domain name, or + a domain name hierarchy, as described in section "TABLE LOOKUP". - The result is of the form transport:nexthop. The trans- - port field specifies a mail delivery transport such as - smtp or local. The nexthop field specifies where and how + The result is of the form transport:nexthop. The trans- + port field specifies a mail delivery transport such as + smtp or local. The nexthop field specifies where and how to deliver mail. More details are given in section "RESULT FORMAT". TABLE LOOKUP With lookups from indexed files such as DB or DBM, or from - networked tables such as NIS, LDAP or SQL, patterns are + networked tables such as NIS, LDAP or SQL, patterns are tried in the order as listed below: user+extension@domain transport:nexthop @@ -75,134 +94,134 @@ TRANSPORT(5) TRANSPORT(5) to nexthop. domain transport:nexthop - Mail for domain is delivered through transport to + Mail for domain is delivered through transport to nexthop. .domain transport:nexthop - Mail for any subdomain of domain is delivered - through transport to nexthop. This applies only + Mail for any subdomain of domain is delivered + through transport to nexthop. This applies only when the string transport_maps is not listed in the parent_domain_matches_subdomains configuration set- - ting. Otherwise, a domain name matches itself and + ting. Otherwise, a domain name matches itself and its subdomains. Note 1: the special pattern * represents any address (i.e. it functions as the wild-card pattern). - Note 2: the null recipient address is looked up as + Note 2: the null recipient address is looked up as $empty_address_recipient@$myhostname (default: mailer-dae- mon@hostname). RESULT FORMAT - The transport field specifies the name of a mail delivery + The transport field specifies the name of a mail delivery transport (the first name of a mail delivery service entry in the Postfix master.cf file). - The interpretation of the nexthop field is transport + The interpretation of the nexthop field is transport dependent. In the case of SMTP, specify host:service for a - non-default server port, and use [host] or [host]:port in - order to disable MX (mail exchanger) DNS lookups. The [] + non-default server port, and use [host] or [host]:port in + order to disable MX (mail exchanger) DNS lookups. The [] form is required when you specify an IP address instead of a hostname. - A null transport and null nexthop result means "do not - change": use the delivery transport and nexthop informa- - tion that would be used when the entire transport table + A null transport and null nexthop result means "do not + change": use the delivery transport and nexthop informa- + tion that would be used when the entire transport table did not exist. - A non-null transport field with a null nexthop field + A non-null transport field with a null nexthop field resets the nexthop information to the recipient domain. - A null transport field with non-null nexthop field does + A null transport field with non-null nexthop field does not modify the transport information. EXAMPLES - In order to deliver internal mail directly, while using a - mail relay for all other mail, specify a null entry for - internal destinations (do not change the delivery trans- - port or the nexthop information) and specify a wildcard + In order to deliver internal mail directly, while using a + mail relay for all other mail, specify a null entry for + internal destinations (do not change the delivery trans- + port or the nexthop information) and specify a wildcard for all other destinations. my.domain : .my.domain : * smtp:outbound-relay.my.domain - In order to send mail for foo.org and its subdomains via + In order to send mail for foo.org and its subdomains via the uucp transport to the UUCP host named foo: foo.org uucp:foo .foo.org uucp:foo - When no nexthop host name is specified, the destination - domain name is used instead. For example, the following - directs mail for user@foo.org via the slow transport to a - mail exchanger for foo.org. The slow transport could be - something that runs at most one delivery process at a + When no nexthop host name is specified, the destination + domain name is used instead. For example, the following + directs mail for user@foo.org via the slow transport to a + mail exchanger for foo.org. The slow transport could be + something that runs at most one delivery process at a time: foo.org slow: When no transport is specified, Postfix uses the transport that matches the address domain class (see TRANSPORT FIELD - discussion above). The following sends all mail for + discussion above). The following sends all mail for foo.org and its subdomains to host gateway.foo.org: foo.org :[gateway.foo.org] .foo.org :[gateway.foo.org] - In the above example, the [] are used to suppress MX - lookups. The result would likely point to your local + In the above example, the [] are used to suppress MX + lookups. The result would likely point to your local machine. - In the case of delivery via SMTP, one may specify host- + In the case of delivery via SMTP, one may specify host- name:service instead of just a host: foo.org smtp:bar.org:2025 - This directs mail for user@foo.org to host bar.org port - 2025. Instead of a numerical port a symbolic name may be - used. Specify [] around the hostname in order to disable + This directs mail for user@foo.org to host bar.org port + 2025. Instead of a numerical port a symbolic name may be + used. Specify [] around the hostname in order to disable MX lookups. The error mailer can be used to bounce mail: - .foo.org error:mail for *.foo.org is not deliv- + .foo.org error:mail for *.foo.org is not deliv- erable - This causes all mail for user@anything.foo.org to be + This causes all mail for user@anything.foo.org to be bounced. REGULAR EXPRESSION TABLES - This section describes how the table lookups change when + This section describes how the table lookups change when the table is given in the form of regular expressions. For - a description of regular expression lookup table syntax, + a description of regular expression lookup table syntax, see regexp_table(5) or pcre_table(5). - Each pattern is a regular expression that is applied to + Each pattern is a regular expression that is applied to the entire domain being looked up. Thus, some.domain.hier- archy is not broken up into parent domains. - Patterns are applied in the order as specified in the - table, until a pattern is found that matches the search + Patterns are applied in the order as specified in the + table, until a pattern is found that matches the search string. - Results are the same as with indexed file lookups, with - the additional feature that parenthesized substrings from + Results are the same as with indexed file lookups, with + the additional feature that parenthesized substrings from the pattern can be interpolated as $1, $2 and so on. CONFIGURATION PARAMETERS - The following main.cf parameters are especially relevant - to this topic. See the Postfix main.cf file for syntax - details and for default values. Use the postfix reload + The following main.cf parameters are especially relevant + to this topic. See the Postfix main.cf file for syntax + details and for default values. Use the postfix reload command after a configuration change. empty_address_recipient - The address that is looked up instead of the null + The address that is looked up instead of the null sender address. parent_domain_matches_subdomains - List of Postfix features that use domain.tld pat- - terns to match sub.domain.tld (as opposed to + List of Postfix features that use domain.tld pat- + terns to match sub.domain.tld (as opposed to requiring .domain.tld patterns). transport_maps @@ -215,7 +234,7 @@ TRANSPORT(5) TRANSPORT(5) regexp_table(5) format of POSIX regular expression tables 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/verify.8.html b/postfix/html/verify.8.html index 181063eb0..ec24b4add 100644 --- a/postfix/html/verify.8.html +++ b/postfix/html/verify.8.html @@ -33,9 +33,9 @@ VERIFY(8) VERIFY(8) Update the status of the specified address. VRFY_ADDR_QUERY address - Look up the status and text of the specified - address. If the status is unknown, a probe is sent - and a default status is returned. + Look up the status, last update time and text of + the specified address. If the status is unknown, a + probe is sent and a default status is returned. The server reply status is one of: diff --git a/postfix/man/Makefile.in b/postfix/man/Makefile.in index 91ede1d16..47656a231 100644 --- a/postfix/man/Makefile.in +++ b/postfix/man/Makefile.in @@ -12,7 +12,8 @@ COMMANDS= man1/postalias.1 man1/postcat.1 man1/postconf.1 man1/postfix.1 \ man1/postmap.1 man1/sendmail.1 man1/mailq.1 man1/newaliases.1 \ man1/postqueue.1 man1/postsuper.1 CONFIG = man5/access.5 man5/aliases.5 man5/canonical.5 man5/relocated.5 \ - man5/transport.5 man5/virtual.5 man5/pcre_table.5 man5/regexp_table.5 + man5/transport.5 man5/virtual.5 man5/pcre_table.5 man5/regexp_table.5 \ + man5/cidr_table.5 man5/tcp_table.5 TOOLS = man1/smtp-sink.1 man1/smtp-source.1 man1/qmqp-sink.1 \ man1/qmqp-source.1 @@ -149,6 +150,9 @@ man5/aliases.5: ../proto/aliases man5/canonical.5: ../proto/canonical ../mantools/srctoman - $? >$@ +man5/cidr_table.5: ../proto/cidr_table + ../mantools/srctoman - $? >$@ + man5/pcre_table.5: ../proto/pcre_table ../mantools/srctoman - $? >$@ @@ -170,6 +174,9 @@ man1/smtp-sink.1: ../src/smtpstone/smtp-sink.c man1/smtp-source.1: ../src/smtpstone/smtp-source.c ../mantools/srctoman $? >$@ +man5/tcp_table.5: ../proto/tcp_table + ../mantools/srctoman - $? >$@ + man1/qmqp-sink.1: ../src/smtpstone/qmqp-sink.c ../mantools/srctoman $? >$@ diff --git a/postfix/man/man5/access.5 b/postfix/man/man5/access.5 index fcdca96b4..5f5ae97a3 100644 --- a/postfix/man/man5/access.5 +++ b/postfix/man/man5/access.5 @@ -106,6 +106,9 @@ order to match subdomains. .IP \fInet\fR Matches any host address in the specified network. A network address is a sequence of one or more octets separated by ".". + +NOTE: use the \fBcidr\fR lookup table type if you want to +specify arbitrary network blocks. .SH ACTIONS .na .nf @@ -194,8 +197,9 @@ The table format does not understand quoting conventions. .SH SEE ALSO .na .nf -postmap(1) create mapping table +postmap(1) create lookup table smtpd(8) smtp server +cidr_table(5) format of CIDR tables pcre_table(5) format of PCRE tables regexp_table(5) format of POSIX regular expression tables .SH LICENSE diff --git a/postfix/man/man5/cidr_table.5 b/postfix/man/man5/cidr_table.5 new file mode 100644 index 000000000..179eb6641 --- /dev/null +++ b/postfix/man/man5/cidr_table.5 @@ -0,0 +1,81 @@ +.TH CIDR_TABLE 5 +.ad +.fi +.SH NAME +cidr_table +\- +format of Postfix CIDR tables +.SH SYNOPSIS +.na +.nf +\fBpostmap -q "\fIstring\fB" cidr:/etc/postfix/\fIfilename\fR + +\fBpostmap -q - cidr:/etc/postfix/\fIfilename\fR <\fIinputfile\fR +.SH DESCRIPTION +.ad +.fi +The Postfix mail system uses optional access control tables. +These tables are usually in \fBdbm\fR or \fBdb\fR format. +Alternatively, access control tables can be specified in CIDR form. + +To find out what types of lookup tables your Postfix system +supports use the \fBpostconf -m\fR command. + +To test lookup tables, use the \fBpostmap\fR command as +described in the SYNOPSIS above. +.SH TABLE FORMAT +.na +.nf +.ad +.fi +The general form of a Postfix CIDR table is: +.IP "\fInetwork_address\fB/\fInetwork_mask result\fR" +When a search string matches the specified network block, +use the corresponding \fIresult\fR value. +.IP "\fInetwork_address result\fR" +When a search string matches the specified network address, +use the corresponding \fIresult\fR value. +.IP "blank lines and comments" +Empty lines and whitespace-only lines are ignored, as +are lines whose first non-whitespace character is a `#'. +.IP "multi-line text" +A logical line starts with non-whitespace text. A line that +starts with whitespace continues a logical line. +.PP +Patterns are applied in the order as specified in the table, until a +pattern is found that matches the search string. +.SH EXAMPLE SMTPD ACCESS MAP +.na +.nf +/etc/postfix/main.cf: +.ti +4 +smtpd_client_restrictions = ... cidr:/etc/postfix/client_cidr ... + +/etc/postfix/client_cidr: +.in +4 +# Rule order matters. Put more specific whitelist entries +# before more general blacklist entries. +192.168.1.1 OK +192.168.0.0/16 REJECT +.in -4 +.SH SEE ALSO +.na +.nf +regexp_table(5) format of regular expression tables +pcre_table(5) format of PCRE tables +tcp_table(5) TCP client/server table lookup protocol +.SH AUTHOR(S) +.na +.nf +The CIDR table lookup code was originally written by: +Jozsef Kadlecsik +kadlec@blackhole.kfki.hu +KFKI Research Institute for Particle and Nuclear Physics +POB. 49 +1525 Budapest, Hungary + +Adopted and adapted by: +Wietse Venema +IBM T.J. Watson Research +P.O. Box 704 +Yorktown Heights, NY 10598, USA diff --git a/postfix/man/man5/pcre_table.5 b/postfix/man/man5/pcre_table.5 index 7fcedcb88..98bd0f7c5 100644 --- a/postfix/man/man5/pcre_table.5 +++ b/postfix/man/man5/pcre_table.5 @@ -8,8 +8,6 @@ format of Postfix PCRE tables .SH SYNOPSIS .na .nf -\fBpcre:/etc/postfix/\fIfilename\fR - \fBpostmap -q "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR \fBpostmap -q - pcre:/etc/postfix/\fIfilename\fR <\fIinputfile\fR @@ -29,8 +27,9 @@ described in the SYNOPSIS above. The general form of a PCRE table is: .IP "\fB/\fIpattern\fB/\fIflags result\fR" -When \fIpattern\fR matches a search string, use the corresponding -\fIresult\fR value. +.IP "\fB!/\fIpattern\fB/\fIflags result\fR" +When \fIpattern\fR matches (does not match) a search string, use +the corresponding \fIresult\fR value. .IP "blank lines and comments" Empty lines and whitespace-only lines are ignored, as are lines whose first non-whitespace character is a `#'. @@ -38,10 +37,12 @@ are lines whose first non-whitespace character is a `#'. A logical line starts with non-whitespace text. A line that starts with whitespace continues a logical line. .IP "\fBif /\fIpattern\fB/\fIflags\fR" +.IP "\fBif !/\fIpattern\fB/\fIflags\fR" .IP "\fBendif\fR" Examine the lines between \fBif\fR..\fBendif\fR only if -\fIpattern\fR matches. The \fBif\fR..\fBendif\fR can nest. -Do not prepend whitespace to patterns inside \fBif\fR..\fBendif\fR. +\fIpattern\fR matches (does not match). The \fBif\fR..\fBendif\fR +can nest. Do not prepend whitespace to patterns inside +\fBif\fR..\fBendif\fR. .PP Each pattern is a perl-like regular expression. The expression delimiter can be any character, except whitespace or characters @@ -114,7 +115,9 @@ pattern is found that matches the search string. Substitution of substrings from the matched expression into the result string is possible using the conventional perl syntax ($1, $2, etc.). The macros in the result string may need to be written as ${n} -or $(n) if they aren't followed by whitespace. +or $(n) if they aren't followed by whitespace. Since negated patterns +(those preceded by \fB!\fR) return a result when the expression does +not match, substitutions are not available for negated patterns. .SH EXAMPLE SMTPD ACCESS MAP .na .nf @@ -147,6 +150,8 @@ or $(n) if they aren't followed by whitespace. .na .nf regexp_table(5) format of POSIX regular expression tables +cidr_table(5) format of CIDR tables +tcp_table(5) TCP client/server table lookup protocol .SH AUTHOR(S) .na .nf diff --git a/postfix/man/man5/regexp_table.5 b/postfix/man/man5/regexp_table.5 index 8b418efe2..0f5173d51 100644 --- a/postfix/man/man5/regexp_table.5 +++ b/postfix/man/man5/regexp_table.5 @@ -8,8 +8,6 @@ format of Postfix regular expression tables .SH SYNOPSIS .na .nf -\fBregexp:/etc/postfix/\fIfilename\fR - \fBpostmap -q "\fIstring\fB" regexp:/etc/postfix/\fIfilename\fR \fBpostmap -q - regexp:/etc/postfix/\fIfilename\fR <\fIinputfile\fR @@ -71,7 +69,9 @@ pattern is found that matches the search string. Substitution of substrings from the matched expression into the result string is possible using $1, $2, etc.. The macros in the result string may need to be written as ${n} or $(n) if they aren't followed -by whitespace. +by whitespace. Since negated patterns (those preceded by \fB!\fR) +return a result when the expression does not match, substitutions are +not available for negated patterns. .SH EXAMPLE SMTPD ACCESS MAP .na .nf @@ -104,6 +104,8 @@ endif .na .nf pcre_table(5) format of PCRE tables +cidr_table(5) format of CIDR tables +tcp_table(5) TCP client/server table lookup protocol .SH AUTHOR(S) .na .nf diff --git a/postfix/man/man5/tcp_table.5 b/postfix/man/man5/tcp_table.5 new file mode 100644 index 000000000..69ab6b4f3 --- /dev/null +++ b/postfix/man/man5/tcp_table.5 @@ -0,0 +1,96 @@ +.TH TCP_TABLE 5 +.ad +.fi +.SH NAME +tcp_table +\- +Postfix client/server table lookup protocol +.SH SYNOPSIS +.na +.nf +\fBpostmap -q "\fIstring\fB" tcp:\fIhost:port\fR + +\fBpostmap -q - regexp:\fIhost:port\fR <\fIinputfile\fR +.SH DESCRIPTION +.ad +.fi +The Postfix mail system uses optional tables for address +rewriting or mail routing. These tables are usually in +\fBdbm\fR or \fBdb\fR format. Alternatively, lookup tables +can be specified as a TCP client/server pair. + +To find out what types of lookup tables your Postfix system +supports use the \fBpostconf -m\fR command. + +To test lookup tables, use the \fBpostmap\fR command as +described in the SYNOPSIS above. +.SH PROTOCOL DESCRIPTION +.na +.nf +.ad +.fi +The TCP map class implements a very simple protocol: the client +sends a request, and the server sends one reply. Requests and +replies are sent as one line of ASCII text, terminated by the +ASCII newline character. Request and reply parameters (see below) +are separated by whitespace. +.SH ENCODING +.na +.nf +.ad +.fi +In request and reply parameters, the character % and any non-printing +and whitespace characters must be replaced by %XX, XX being the +corresponding ASCII hexadecimal character value. The hexadecimal codes +can be specified in any case (upper, lower, mixed). +.SH REQUEST FORMAT +.na +.nf +.ad +.fi +Requests are strings that serve as lookup key in the simulated +table. +.IP "\fBget\fR SPACE \fIkey\fR NEWLINE" +Look up data under the specified key. +.IP "\fBput\fR SPACE \fIkey\fR SPACE \fIvalue\fR NEWLINE" +This request is currently not implemented. +.SH REPLY FORMAT +.na +.nf +.ad +.fi +Replies must be no longer than 4096 characters including the +newline terminator, and must have the following form: +.IP "\fB500\fR SPACE \fIoptional-text\fR NEWLINE" +In case of a lookup request, the requested data does not exist. +In case of an update request, the request was rejected. +.IP "\fB400\fR SPACE \fIoptional-text\fR NEWLINE" +This indicates an error condition. The text gives the nature of +the problem. The client should retry the request later. +.IP "\fB200\fR SPACE \fItext\fR NEWLINE" +The request was successful. In the case of a lookup request, +the text contains an encoded version of the requested data. +Otherwise the text is optional. +.SH SEE ALSO +.na +.nf +regexp_table(5) format of regular expression tables +pcre_table(5) format of PCRE tables +cidr_table(5) format of CIDR tables +.SH BUGS +.ad +.fi +Only the lookup method is currently implemented. +.SH LICENSE +.na +.nf +.ad +.fi +The Secure Mailer license must be distributed with this software. +.SH AUTHOR(S) +.na +.nf +Wietse Venema +IBM T.J. Watson Research +P.O. Box 704 +Yorktown Heights, NY 10598, USA diff --git a/postfix/man/man5/transport.5 b/postfix/man/man5/transport.5 index e4c69f372..05eba4ca1 100644 --- a/postfix/man/man5/transport.5 +++ b/postfix/man/man5/transport.5 @@ -20,6 +20,21 @@ The optional \fBtransport\fR table specifies a mapping from email addresses to message delivery transports and/or relay hosts. The mapping is used by the \fBtrivial-rewrite\fR(8) daemon. +This mapping overrides the default routing that is built into +Postfix: +.IP \fBmydestination\fR +A list of domains that is by default delivered via +\fB$local_transport\fR. +.IP \fBvirtual_mailbox_domains\fR +A list of domains that is by default delivered via +\fB$virtual_transport\fR. +.IP \fBrelay_domains\fR +A list of domains that is by default delivered via +\fB$relay_transport\fR. +.IP "any other destination" +Mail for any other destination is by default delivered via +\fB$default_transport\fR. +.PP Normally, the \fBtransport\fR table is specified as a text file that serves as input to the \fBpostmap\fR(1) command. The result, an indexed file in \fBdbm\fR or \fBdb\fR format, is used diff --git a/postfix/man/man8/cleanup.8 b/postfix/man/man8/cleanup.8 index fdadfa4a8..0e21c52d2 100644 --- a/postfix/man/man8/cleanup.8 +++ b/postfix/man/man8/cleanup.8 @@ -150,6 +150,11 @@ substitution is done before all other address rewriting. .IP \fBcanonical_maps\fR Address mapping lookup table for sender and recipient addresses in envelopes and headers. +.IP \fBenable_original_recipient\fR +Enable support for the X-Original-To message header, which is +needed for multi-recipient mailboxes. When this is enabled, Postfix +performs duplicate elimination on (original recipient, rewritten +recipient) pairs, instead of looking at the rewritten recipient only. .IP \fBrecipient_canonical_maps\fR Address mapping lookup table for envelope and header recipient addresses. diff --git a/postfix/man/man8/smtp.8 b/postfix/man/man8/smtp.8 index 1ca75585d..10484ffac 100644 --- a/postfix/man/man8/smtp.8 +++ b/postfix/man/man8/smtp.8 @@ -27,10 +27,6 @@ The SMTP client looks up a list of mail exchanger addresses for the destination host, sorts the list by preference, and connects to each listed address until it finds a server that responds. -When the domain or host is specified as a comma/whitespace -separated list, the SMTP client repeats the above process -for all destinations until it finds a server that responds. - Once the SMTP client has received the server greeting banner, no error will cause it to proceed to the next address on the mail exchanger list. Instead, the message is either bounced, or its @@ -134,6 +130,9 @@ Length limit for SMTP message content lines. Zero means no limit. Some SMTP servers misbehave on long lines. .IP \fBsmtp_helo_name\fR The hostname to be used in HELO and EHLO commands. +.IP \fBsmtp_quote_rfc821_envelope\fR +Whether or not to quote MAIL FROM and RCPT TO addresses as +per the rules laid out in RFC 821. .IP \fBsmtp_skip_4xx_greeting\fR Skip servers that greet us with a 4xx status code. .IP \fBsmtp_skip_5xx_greeting\fR diff --git a/postfix/man/man8/smtpd.8 b/postfix/man/man8/smtpd.8 index 8646cd8bb..8c63d52aa 100644 --- a/postfix/man/man8/smtpd.8 +++ b/postfix/man/man8/smtpd.8 @@ -111,6 +111,24 @@ Disallow anonymous logins. Maps that specify the SASL login name that owns a MAIL FROM sender address. Used by the \fBreject_sender_login_mismatch\fR sender anti-spoofing restriction. +.SH "Pass-through proxy" +.ad +.fi +.ad +Optionally, the Postfix SMTP server can be configured to +forward all mail to a proxy server, for example a real-time +content filter. This proxy server should support the same +MAIL FROM and RCPT TO command syntax as Postfix, but does not +need to support ESMTP command pipelining. +.IP \fBsmtpd_proxy_filter\fR +The \fIhost:port\fR of the SMTP proxy server. The \fIhost\fR +or \fIhost:\fR portion is optional. +.IP \fBsmtpd_proxy_timeout\fR +Timeout for connecting to, sending to and receiving from +the SMTP proxy server. +.IP \fBsmtpd_proxy_ehlo\fR +The hostname to use when sending an EHLO command to the +SMTP proxy server. .SH Miscellaneous .ad .fi @@ -259,6 +277,21 @@ are eligible for the \fBpermit_mx_backup\fR feature. Restrict what domains this mail system will relay mail to. The domains are routed to the delivery agent specified with the \fBrelay_transport\fR setting. +.SH "Sender/recipient address verification" +.ad +.fi +Address verification is implemented by sending probe email +messages that are not actually delivered, and is enabled +via the reject_unverified_{sender,recipient} access restriction. +The status of verification probes is maintained by the address +verification service. +.IP \fBaddress_verify_poll_count\fR +How many times to query the address verification service +for completion of an address verification request. +Specify 0 to implement a simple form of greylisting. +.IP \fBaddress_verify_poll_delay\fR +Time to wait after querying the address verification service +for completion of an address verification request. .SH "UCE control responses" .ad .fi @@ -305,10 +338,11 @@ Response code when a recipient address is known to be undeliverable. .SH SEE ALSO .na .nf -trivial-rewrite(8) address resolver cleanup(8) message canonicalization master(8) process manager syslogd(8) system logging +trivial-rewrite(8) address resolver +verify(8) address verification service .SH LICENSE .na .nf diff --git a/postfix/man/man8/verify.8 b/postfix/man/man8/verify.8 index 61a903edf..ece5ba89f 100644 --- a/postfix/man/man8/verify.8 +++ b/postfix/man/man8/verify.8 @@ -35,7 +35,8 @@ This server implements the following requests: .IP "\fBVRFY_ADDR_UPDATE\fI address status text\fR" Update the status of the specified address. .IP "\fBVRFY_ADDR_QUERY\fI address\fR" -Look up the \fIstatus\fR and \fItext\fR of the specified address. +Look up the \fIstatus\fR, \fIlast update time\fR and \fItext\fR +of the specified address. If the status is unknown, a probe is sent and a default status is returned. .PP diff --git a/postfix/proto/Makefile.in b/postfix/proto/Makefile.in index fadc14678..a5b3de8ce 100644 --- a/postfix/proto/Makefile.in +++ b/postfix/proto/Makefile.in @@ -4,7 +4,7 @@ SHELL = /bin/sh CONFIG = ../conf/access ../conf/aliases ../conf/canonical ../conf/relocated \ ../conf/transport ../conf/virtual ../conf/pcre_table \ - ../conf/regexp_table + ../conf/regexp_table ../conf/cidr_table ../conf/tcp_table AWK = awk '{ print; if (NR == 1) print ".pl 9999" }' @@ -30,6 +30,9 @@ clobber: ../conf/canonical: canonical srctoman - $? | $(AWK) | nroff -man | col -bx | uniq | sed 's/^/# /' >$@ +../conf/cidr_table: cidr_table + srctoman - $? | $(AWK) | nroff -man | col -bx | uniq | sed 's/^/# /' >$@ + ../conf/pcre_table: pcre_table srctoman - $? | $(AWK) | nroff -man | col -bx | uniq | sed 's/^/# /' >$@ @@ -39,6 +42,9 @@ clobber: ../conf/relocated: relocated srctoman - $? | $(AWK) | nroff -man | col -bx | uniq | sed 's/^/# /' >$@ +../conf/tcp_table: tcp_table + srctoman - $? | $(AWK) | nroff -man | col -bx | uniq | sed 's/^/# /' >$@ + ../conf/transport: transport srctoman - $? | $(AWK) | nroff -man | col -bx | uniq | sed 's/^/# /' >$@ diff --git a/postfix/proto/access b/postfix/proto/access index f1bbc99f1..d5cddcc06 100644 --- a/postfix/proto/access +++ b/postfix/proto/access @@ -92,6 +92,9 @@ # .IP \fInet\fR # Matches any host address in the specified network. A network # address is a sequence of one or more octets separated by ".". +# +# NOTE: use the \fBcidr\fR lookup table type if you want to +# specify arbitrary network blocks. # ACTIONS # .ad # .fi @@ -172,8 +175,9 @@ # BUGS # The table format does not understand quoting conventions. # SEE ALSO -# postmap(1) create mapping table +# postmap(1) create lookup table # smtpd(8) smtp server +# cidr_table(5) format of CIDR tables # pcre_table(5) format of PCRE tables # regexp_table(5) format of POSIX regular expression tables # LICENSE diff --git a/postfix/proto/cidr_table b/postfix/proto/cidr_table new file mode 100644 index 000000000..13273a064 --- /dev/null +++ b/postfix/proto/cidr_table @@ -0,0 +1,68 @@ +#++ +# NAME +# cidr_table 5 +# SUMMARY +# format of Postfix CIDR tables +# SYNOPSIS +# \fBpostmap -q "\fIstring\fB" cidr:/etc/postfix/\fIfilename\fR +# +# \fBpostmap -q - cidr:/etc/postfix/\fIfilename\fR <\fIinputfile\fR +# DESCRIPTION +# The Postfix mail system uses optional access control tables. +# These tables are usually in \fBdbm\fR or \fBdb\fR format. +# Alternatively, access control tables can be specified in CIDR form. +# +# To find out what types of lookup tables your Postfix system +# supports use the \fBpostconf -m\fR command. +# +# To test lookup tables, use the \fBpostmap\fR command as +# described in the SYNOPSIS above. +# TABLE FORMAT +# .ad +# .fi +# The general form of a Postfix CIDR table is: +# .IP "\fInetwork_address\fB/\fInetwork_mask result\fR" +# When a search string matches the specified network block, +# use the corresponding \fIresult\fR value. +# .IP "\fInetwork_address result\fR" +# When a search string matches the specified network address, +# use the corresponding \fIresult\fR value. +# .IP "blank lines and comments" +# Empty lines and whitespace-only lines are ignored, as +# are lines whose first non-whitespace character is a `#'. +# .IP "multi-line text" +# A logical line starts with non-whitespace text. A line that +# starts with whitespace continues a logical line. +# .PP +# Patterns are applied in the order as specified in the table, until a +# pattern is found that matches the search string. +# EXAMPLE SMTPD ACCESS MAP +# /etc/postfix/main.cf: +# .ti +4 +# smtpd_client_restrictions = ... cidr:/etc/postfix/client_cidr ... +# +# /etc/postfix/client_cidr: +# .in +4 +# # Rule order matters. Put more specific whitelist entries +# # before more general blacklist entries. +# 192.168.1.1 OK +# 192.168.0.0/16 REJECT +# .in -4 +# SEE ALSO +# regexp_table(5) format of regular expression tables +# pcre_table(5) format of PCRE tables +# tcp_table(5) TCP client/server table lookup protocol +# AUTHOR(S) +# The CIDR table lookup code was originally written by: +# Jozsef Kadlecsik +# kadlec@blackhole.kfki.hu +# KFKI Research Institute for Particle and Nuclear Physics +# POB. 49 +# 1525 Budapest, Hungary +# +# Adopted and adapted by: +# Wietse Venema +# IBM T.J. Watson Research +# P.O. Box 704 +# Yorktown Heights, NY 10598, USA +#-- diff --git a/postfix/proto/pcre_table b/postfix/proto/pcre_table index eab2ebbee..08f595484 100644 --- a/postfix/proto/pcre_table +++ b/postfix/proto/pcre_table @@ -4,8 +4,6 @@ # SUMMARY # format of Postfix PCRE tables # SYNOPSIS -# \fBpcre:/etc/postfix/\fIfilename\fR -# # \fBpostmap -q "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR # # \fBpostmap -q - pcre:/etc/postfix/\fIfilename\fR <\fIinputfile\fR @@ -23,8 +21,9 @@ # # The general form of a PCRE table is: # .IP "\fB/\fIpattern\fB/\fIflags result\fR" -# When \fIpattern\fR matches a search string, use the corresponding -# \fIresult\fR value. +# .IP "\fB!/\fIpattern\fB/\fIflags result\fR" +# When \fIpattern\fR matches (does not match) a search string, use +# the corresponding \fIresult\fR value. # .IP "blank lines and comments" # Empty lines and whitespace-only lines are ignored, as # are lines whose first non-whitespace character is a `#'. @@ -32,10 +31,12 @@ # A logical line starts with non-whitespace text. A line that # starts with whitespace continues a logical line. # .IP "\fBif /\fIpattern\fB/\fIflags\fR" +# .IP "\fBif !/\fIpattern\fB/\fIflags\fR" # .IP "\fBendif\fR" # Examine the lines between \fBif\fR..\fBendif\fR only if -# \fIpattern\fR matches. The \fBif\fR..\fBendif\fR can nest. -# Do not prepend whitespace to patterns inside \fBif\fR..\fBendif\fR. +# \fIpattern\fR matches (does not match). The \fBif\fR..\fBendif\fR +# can nest. Do not prepend whitespace to patterns inside +# \fBif\fR..\fBendif\fR. # .PP # Each pattern is a perl-like regular expression. The expression # delimiter can be any character, except whitespace or characters @@ -108,7 +109,9 @@ # Substitution of substrings from the matched expression into the result # string is possible using the conventional perl syntax ($1, $2, etc.). # The macros in the result string may need to be written as ${n} -# or $(n) if they aren't followed by whitespace. +# or $(n) if they aren't followed by whitespace. Since negated patterns +# (those preceded by \fB!\fR) return a result when the expression does +# not match, substitutions are not available for negated patterns. # EXAMPLE SMTPD ACCESS MAP # # Protect your outgoing majordomo exploders # /^(?!owner-)(.*)-outgoing@(.*)/ 550 Use ${1}@${2} instead @@ -133,6 +136,8 @@ # # Put your own body patterns here. # SEE ALSO # regexp_table(5) format of POSIX regular expression tables +# cidr_table(5) format of CIDR tables +# tcp_table(5) TCP client/server table lookup protocol # AUTHOR(S) # The PCRE table lookup code was originally written by: # Andrew McNamara diff --git a/postfix/proto/regexp_table b/postfix/proto/regexp_table index 6c7ab4553..2e32819a7 100644 --- a/postfix/proto/regexp_table +++ b/postfix/proto/regexp_table @@ -4,8 +4,6 @@ # SUMMARY # format of Postfix regular expression tables # SYNOPSIS -# \fBregexp:/etc/postfix/\fIfilename\fR -# # \fBpostmap -q "\fIstring\fB" regexp:/etc/postfix/\fIfilename\fR # # \fBpostmap -q - regexp:/etc/postfix/\fIfilename\fR <\fIinputfile\fR @@ -65,7 +63,9 @@ # Substitution of substrings from the matched expression into the result # string is possible using $1, $2, etc.. The macros in the result string # may need to be written as ${n} or $(n) if they aren't followed -# by whitespace. +# by whitespace. Since negated patterns (those preceded by \fB!\fR) +# return a result when the expression does not match, substitutions are +# not available for negated patterns. # EXAMPLE SMTPD ACCESS MAP # # Disallow sender-specified routing. This is a must if you relay mail # # for other domains. @@ -90,6 +90,8 @@ # # Put your own body patterns here. # SEE ALSO # pcre_table(5) format of PCRE tables +# cidr_table(5) format of CIDR tables +# tcp_table(5) TCP client/server table lookup protocol # AUTHOR(S) # The regexp table lookup code was originally written by: # LaMont Jones diff --git a/postfix/proto/tcp_table b/postfix/proto/tcp_table new file mode 100644 index 000000000..e099ea258 --- /dev/null +++ b/postfix/proto/tcp_table @@ -0,0 +1,75 @@ +#++ +# NAME +# tcp_table 5 +# SUMMARY +# Postfix client/server table lookup protocol +# SYNOPSIS +# \fBpostmap -q "\fIstring\fB" tcp:\fIhost:port\fR +# +# \fBpostmap -q - regexp:\fIhost:port\fR <\fIinputfile\fR +# DESCRIPTION +# The Postfix mail system uses optional tables for address +# rewriting or mail routing. These tables are usually in +# \fBdbm\fR or \fBdb\fR format. Alternatively, lookup tables +# can be specified as a TCP client/server pair. +# +# To find out what types of lookup tables your Postfix system +# supports use the \fBpostconf -m\fR command. +# +# To test lookup tables, use the \fBpostmap\fR command as +# described in the SYNOPSIS above. +# PROTOCOL DESCRIPTION +# .ad +# .fi +# The TCP map class implements a very simple protocol: the client +# sends a request, and the server sends one reply. Requests and +# replies are sent as one line of ASCII text, terminated by the +# ASCII newline character. Request and reply parameters (see below) +# are separated by whitespace. +# ENCODING +# .ad +# .fi +# In request and reply parameters, the character % and any non-printing +# and whitespace characters must be replaced by %XX, XX being the +# corresponding ASCII hexadecimal character value. The hexadecimal codes +# can be specified in any case (upper, lower, mixed). +# REQUEST FORMAT +# .ad +# .fi +# Requests are strings that serve as lookup key in the simulated +# table. +# .IP "\fBget\fR SPACE \fIkey\fR NEWLINE" +# Look up data under the specified key. +# .IP "\fBput\fR SPACE \fIkey\fR SPACE \fIvalue\fR NEWLINE" +# This request is currently not implemented. +# REPLY FORMAT +# .ad +# .fi +# Replies must be no longer than 4096 characters including the +# newline terminator, and must have the following form: +# .IP "\fB500\fR SPACE \fIoptional-text\fR NEWLINE" +# In case of a lookup request, the requested data does not exist. +# In case of an update request, the request was rejected. +# .IP "\fB400\fR SPACE \fIoptional-text\fR NEWLINE" +# This indicates an error condition. The text gives the nature of +# the problem. The client should retry the request later. +# .IP "\fB200\fR SPACE \fItext\fR NEWLINE" +# The request was successful. In the case of a lookup request, +# the text contains an encoded version of the requested data. +# Otherwise the text is optional. +# SEE ALSO +# regexp_table(5) format of regular expression tables +# pcre_table(5) format of PCRE tables +# cidr_table(5) format of CIDR tables +# BUGS +# Only the lookup method is currently implemented. +# 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 +#--*/ diff --git a/postfix/proto/transport b/postfix/proto/transport index e9ca553a0..122fdfb5d 100644 --- a/postfix/proto/transport +++ b/postfix/proto/transport @@ -14,6 +14,21 @@ # addresses to message delivery transports and/or relay hosts. The # mapping is used by the \fBtrivial-rewrite\fR(8) daemon. # +# This mapping overrides the default routing that is built into +# Postfix: +# .IP \fBmydestination\fR +# A list of domains that is by default delivered via +# \fB$local_transport\fR. +# .IP \fBvirtual_mailbox_domains\fR +# A list of domains that is by default delivered via +# \fB$virtual_transport\fR. +# .IP \fBrelay_domains\fR +# A list of domains that is by default delivered via +# \fB$relay_transport\fR. +# .IP "any other destination" +# Mail for any other destination is by default delivered via +# \fB$default_transport\fR. +# .PP # Normally, the \fBtransport\fR table is specified as a text file # that serves as input to the \fBpostmap\fR(1) command. # The result, an indexed file in \fBdbm\fR or \fBdb\fR format, is used diff --git a/postfix/src/bounce/Makefile.in b/postfix/src/bounce/Makefile.in index aafc24afb..232875a8f 100644 --- a/postfix/src/bounce/Makefile.in +++ b/postfix/src/bounce/Makefile.in @@ -132,6 +132,8 @@ bounce_notify_util.o: ../../include/vstring.h bounce_notify_util.o: ../../include/vbuf.h bounce_notify_util.o: ../../include/vstream.h bounce_notify_util.o: ../../include/line_wrap.h +bounce_notify_util.o: ../../include/stringops.h +bounce_notify_util.o: ../../include/xtext.h bounce_notify_util.o: ../../include/mail_queue.h bounce_notify_util.o: ../../include/quote_822_local.h bounce_notify_util.o: ../../include/quote_flags.h diff --git a/postfix/src/bounce/bounce_notify_util.c b/postfix/src/bounce/bounce_notify_util.c index c14f0482b..e04a71b41 100644 --- a/postfix/src/bounce/bounce_notify_util.c +++ b/postfix/src/bounce/bounce_notify_util.c @@ -155,6 +155,8 @@ #include #include #include +#include +#include /* Global library. */ @@ -210,10 +212,19 @@ static BOUNCE_INFO *bounce_mail_alloc(const char *service, } bounce_info->flush = flush; bounce_info->buf = vstring_alloc(100); + bounce_info->sender = vstring_alloc(100); bounce_info->arrival_time = 0; bounce_info->orig_offs = 0; bounce_info->log_handle = log_handle; + /* + * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and + * must ensure it is valid. + */ + bounce_info->mail_name = mystrdup(var_mail_name); + translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]", + "-----------------"); + /* * Compute a supposedly unique boundary string. This assumes that a queue * ID and a hostname contain acceptable characters for a boundary string, @@ -247,7 +258,16 @@ static BOUNCE_INFO *bounce_mail_alloc(const char *service, if (rec_type == REC_TYPE_TIME && bounce_info->arrival_time == 0) { if ((bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0) bounce_info->arrival_time = 0; + } else if (rec_type == REC_TYPE_FROM) { + quote_822_local_flags(bounce_info->sender, + VSTRING_LEN(bounce_info->buf) ? + STR(bounce_info->buf) : + mail_addr_mail_daemon(), 0); } else if (rec_type == REC_TYPE_MESG) { + /* XXX Future: sender+recipient after message content. */ + if (VSTRING_LEN(bounce_info->sender) == 0) + msg_warn("%s: no sender before message content record", + bounce_info->queue_id); bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp); break; } @@ -322,6 +342,8 @@ void bounce_mail_free(BOUNCE_INFO *bounce_info) bounce_info->queue_id, bounce_info->queue_name, bounce_info->queue_id); vstring_free(bounce_info->buf); + vstring_free(bounce_info->sender); + myfree(bounce_info->mail_name); myfree((char *) bounce_info->mime_boundary); myfree((char *) bounce_info); } @@ -542,6 +564,11 @@ int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) #if 0 post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever"); #endif + post_mail_fprintf(bounce, "X-%s-Queue-ID: %s", + bounce_info->mail_name, bounce_info->queue_id); + if (VSTRING_LEN(bounce_info->sender) > 0) + post_mail_fprintf(bounce, "X-%s-Sender: rfc822; %s", + bounce_info->mail_name, STR(bounce_info->sender)); if (bounce_info->arrival_time > 0) post_mail_fprintf(bounce, "Arrival-Date: %s", mail_date(bounce_info->arrival_time)); @@ -552,25 +579,21 @@ int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) { - char *fixed_mail_name; - post_mail_fputs(bounce, ""); post_mail_fprintf(bounce, "Final-Recipient: rfc822; %s", bounce_info->log_handle->recipient); - if (bounce_info->log_handle->orig_rcpt) + if (bounce_info->log_handle->orig_rcpt) { + xtext_quote(bounce_info->buf, bounce_info->log_handle->orig_rcpt, "+="); post_mail_fprintf(bounce, "Original-Recipient: rfc822; %s", - bounce_info->log_handle->orig_rcpt); + STR(bounce_info->buf)); + } post_mail_fprintf(bounce, "Action: %s", bounce_info->flush == BOUNCE_MSG_FAIL ? "failed" : bounce_info->log_handle->dsn_action); post_mail_fprintf(bounce, "Status: %s", bounce_info->log_handle->dsn_status); - /* RFC 1894: diagnostic-type is an RFC 822 atom. */ - fixed_mail_name = mystrdup(var_mail_name); - translit(fixed_mail_name, " \t\r\n()<>@,;:\\\".[]", "-----------------"); bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s", - fixed_mail_name, bounce_info->log_handle->text); - myfree(fixed_mail_name); + bounce_info->mail_name, bounce_info->log_handle->text); #if 0 post_mail_fprintf(bounce, "Last-Attempt-Date: %s", bounce_info->log_handle->log_time); diff --git a/postfix/src/bounce/bounce_service.h b/postfix/src/bounce/bounce_service.h index 3c919e99b..c3c56af82 100644 --- a/postfix/src/bounce/bounce_service.h +++ b/postfix/src/bounce/bounce_service.h @@ -69,10 +69,12 @@ typedef struct { const char *mime_boundary; /* for MIME */ int flush; /* 0=defer, other=bounce */ VSTRING *buf; /* scratch pad */ + VSTRING *sender; /* envelope sender */ VSTREAM *orig_fp; /* open queue file */ long orig_offs; /* start of content */ time_t arrival_time; /* time of arrival */ BOUNCE_LOG *log_handle; /* open logfile */ + char *mail_name; /* $mail_name, cooked */ } BOUNCE_INFO; extern BOUNCE_INFO *bounce_mail_init(const char *, const char *, const char *, const char *, int); diff --git a/postfix/src/cleanup/cleanup.c b/postfix/src/cleanup/cleanup.c index 0d8afa8b4..b0dd7911e 100644 --- a/postfix/src/cleanup/cleanup.c +++ b/postfix/src/cleanup/cleanup.c @@ -136,6 +136,11 @@ /* .IP \fBcanonical_maps\fR /* Address mapping lookup table for sender and recipient addresses /* in envelopes and headers. +/* .IP \fBenable_original_recipient\fR +/* Enable support for the X-Original-To message header, which is +/* needed for multi-recipient mailboxes. When this is enabled, Postfix +/* performs duplicate elimination on (original recipient, rewritten +/* recipient) pairs, instead of looking at the rewritten recipient only. /* .IP \fBrecipient_canonical_maps\fR /* Address mapping lookup table for envelope and header recipient /* addresses. @@ -342,6 +347,7 @@ int main(int argc, char **argv) */ single_server_main(argc, argv, cleanup_service, MAIL_SERVER_INT_TABLE, cleanup_int_table, + MAIL_SERVER_BOOL_TABLE, cleanup_bool_table, MAIL_SERVER_STR_TABLE, cleanup_str_table, MAIL_SERVER_TIME_TABLE, cleanup_time_table, MAIL_SERVER_PRE_INIT, cleanup_pre_jail, diff --git a/postfix/src/cleanup/cleanup.h b/postfix/src/cleanup/cleanup.h index bb09e8fe1..3077dc99b 100644 --- a/postfix/src/cleanup/cleanup.h +++ b/postfix/src/cleanup/cleanup.h @@ -127,6 +127,7 @@ extern void cleanup_all(void); extern void cleanup_pre_jail(char *, char **); extern void cleanup_post_jail(char *, char **); extern CONFIG_INT_TABLE cleanup_int_table[]; +extern CONFIG_BOOL_TABLE cleanup_bool_table[]; extern CONFIG_STR_TABLE cleanup_str_table[]; extern CONFIG_TIME_TABLE cleanup_time_table[]; diff --git a/postfix/src/cleanup/cleanup_init.c b/postfix/src/cleanup/cleanup_init.c index dcd1dfbf0..30585e7d1 100644 --- a/postfix/src/cleanup/cleanup_init.c +++ b/postfix/src/cleanup/cleanup_init.c @@ -8,6 +8,8 @@ /* /* CONFIG_INT_TABLE cleanup_int_table[]; /* +/* CONFIG_BOOL_TABLE cleanup_bool_table[]; +/* /* CONFIG_STR_TABLE cleanup_str_table[]; /* /* CONFIG_TIME_TABLE cleanup_time_table[]; @@ -102,6 +104,7 @@ char *var_mimehdr_checks; /* mime header checks */ char *var_nesthdr_checks; /* nested header checks */ char *var_body_checks; /* any body checks */ int var_dup_filter_limit; /* recipient dup filter */ +bool var_enable_orcpt; /* Include orcpt in dup filter? */ char *var_empty_addr; /* destination of bounced bounces */ int var_delay_warn_time; /* delay that triggers warning */ char *var_prop_extension; /* propagate unmatched extension */ @@ -125,6 +128,11 @@ CONFIG_INT_TABLE cleanup_int_table[] = { 0, }; +CONFIG_BOOL_TABLE cleanup_bool_table[] = { + VAR_ENABLE_ORCPT, DEF_ENABLE_ORCPT, &var_enable_orcpt, + 0, +}; + CONFIG_TIME_TABLE cleanup_time_table[] = { VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0, 0, diff --git a/postfix/src/cleanup/cleanup_out_recipient.c b/postfix/src/cleanup/cleanup_out_recipient.c index 97129487e..b70747673 100644 --- a/postfix/src/cleanup/cleanup_out_recipient.c +++ b/postfix/src/cleanup/cleanup_out_recipient.c @@ -68,6 +68,12 @@ void cleanup_out_recipient(CLEANUP_STATE *state, const char *orcpt, ARGV *argv; char **cpp; + /* + * XXX Not elegant, but eliminates complexity in the record reading loop. + */ + if (!var_enable_orcpt) + orcpt = ""; + /* * Distinguish between different original recipient addresses that map * onto the same mailbox. The recipient will use our original recipient diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 612a03e2d..062735f6b 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -77,7 +77,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \ off_cvt quote_822_local rec2stream recdump resolve_clnt \ resolve_local rewrite_clnt stream2rec string_list tok822_parse \ quote_821_local mail_conf_time mime_state strip_addr \ - virtual8_maps verify_clnt + virtual8_maps verify_clnt xtext LIBS = ../../lib/libutil.a LIB_DIR = ../../lib @@ -240,9 +240,14 @@ verify_clnt: $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) mv junk $@.o +xtext: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + tests: tok822_test mime_test mime_nest mime_8bit mime_dom mime_trunc \ mime_cvt mime_cvt2 mime_cvt3 strip_addr_test tok822_limit_test \ - virtual8_test + virtual8_test xtext_test tok822_test: tok822_parse tok822_parse.in tok822_parse.ref ./tok822_parse tok822_parse.tmp 2>&1 @@ -306,6 +311,12 @@ virtual8_test: virtual8_maps virtual8_map virtual8.in virtual8.ref \ diff virtual8.ref virtual8.tmp rm -f virtual8.tmp virtual8_map.db +xtext_test: xtext + ./xtext xtext.tmp + od -cb xtext.ref + cmp xtext.ref xtext.tmp + rm -f xtext.ref xtext.tmp + # Requires: Postfix running, root privileges rewrite_clnt_test: rewrite_clnt rewrite_clnt.in rewrite_clnt.ref @@ -1344,7 +1355,7 @@ virtual8_maps.o: strip_addr.h virtual8_maps.o: virtual8_maps.h xtext.o: xtext.c xtext.o: ../../include/sys_defs.h -xtext.o: ../../include/vstream.h -xtext.o: ../../include/vbuf.h +xtext.o: ../../include/msg.h xtext.o: ../../include/vstring.h +xtext.o: ../../include/vbuf.h xtext.o: xtext.h diff --git a/postfix/src/global/cleanup_user.h b/postfix/src/global/cleanup_user.h index b4fc4249e..bfb7f6723 100644 --- a/postfix/src/global/cleanup_user.h +++ b/postfix/src/global/cleanup_user.h @@ -38,6 +38,7 @@ #define CLEANUP_STAT_CONT (1<<3) /* Message content rejected */ #define CLEANUP_STAT_HOPS (1<<4) /* Too many hops */ #define CLEANUP_STAT_RCPT (1<<6) /* No recipients found */ +#define CLEANUP_STAT_PROXY (1<<7) /* Proxy reject */ /* * These are set when we can't bounce even if we were asked to. diff --git a/postfix/src/global/header_token.c b/postfix/src/global/header_token.c index c95207fb3..7a2bf0597 100644 --- a/postfix/src/global/header_token.c +++ b/postfix/src/global/header_token.c @@ -176,11 +176,14 @@ int header_token(HEADER_TOKEN *token, int token_len, if (ch == '"') break; if (ch == '\n') { /* unfold */ - len = LEN(token_buffer); - while (len > 0 && IS_SPACE_TAB_CR_LF(STR(token_buffer)[len - 1])) - len--; - if (len < LEN(token_buffer)) - vstring_truncate(token_buffer, len); + if (tok_count < token_len) { + len = LEN(token_buffer); + while (len > 0 + && IS_SPACE_TAB_CR_LF(STR(token_buffer)[len - 1])) + len--; + if (len < LEN(token_buffer)) + vstring_truncate(token_buffer, len); + } continue; } if (ch == '\\') { diff --git a/postfix/src/global/log_adhoc.c b/postfix/src/global/log_adhoc.c index e42df7483..03a3539f0 100644 --- a/postfix/src/global/log_adhoc.c +++ b/postfix/src/global/log_adhoc.c @@ -115,9 +115,7 @@ void vlog_adhoc(const char *id, const char *orig_rcpt, int delay = time((time_t *) 0) - entry; vstring_vsprintf(why, fmt, ap); - if (orig_rcpt == 0) - orig_rcpt = ""; - if (strcasecmp(recipient, orig_rcpt) != 0) + if (orig_rcpt && *orig_rcpt && strcasecmp(recipient, orig_rcpt) != 0) msg_info("%s: to=<%s>, orig_to=<%s>, relay=%s, delay=%d, status=%s (%s)", id, recipient, orig_rcpt, relay, delay, status, vstring_str(why)); else diff --git a/postfix/src/global/mail_conf_int.c b/postfix/src/global/mail_conf_int.c index da4f0ddab..e00e1de0a 100644 --- a/postfix/src/global/mail_conf_int.c +++ b/postfix/src/global/mail_conf_int.c @@ -112,9 +112,9 @@ static int convert_mail_conf_int(const char *name, int *intval) static void check_mail_conf_int(const char *name, int intval, int min, int max) { if (min && intval < min) - msg_fatal("invalid %s: %d (min %d)", name, intval, min); + msg_fatal("invalid %s parameter value %d < %d", name, intval, min); if (max && intval > max) - msg_fatal("invalid %s: %d (max %d)", name, intval, max); + msg_fatal("invalid %s parameter value %d > %d", name, intval, max); } /* get_mail_conf_int - evaluate integer-valued configuration variable */ diff --git a/postfix/src/global/mail_conf_str.c b/postfix/src/global/mail_conf_str.c index fba5ead46..1c2c170ab 100644 --- a/postfix/src/global/mail_conf_str.c +++ b/postfix/src/global/mail_conf_str.c @@ -87,10 +87,10 @@ static void check_mail_conf_str(const char *name, const char *strval, int len = strlen(strval); if (min && len < min) - msg_fatal("bad string length (%d < %d): %s = %s", + msg_fatal("bad string length %d < %d: %s = %s", len, min, name, strval); if (max && len > max) - msg_fatal("bad string length (%d > %d): %s = %s", + msg_fatal("bad string length %d > %d: %s = %s", len, max, name, strval); } diff --git a/postfix/src/global/mail_copy.c b/postfix/src/global/mail_copy.c index dc51da9ea..496625764 100644 --- a/postfix/src/global/mail_copy.c +++ b/postfix/src/global/mail_copy.c @@ -165,8 +165,15 @@ int mail_copy(const char *sender, if (flags & MAIL_COPY_ORIG_RCPT) { if (orig_rcpt == 0) msg_panic("%s: null orig_rcpt", myname); - quote_822_local(buf, orig_rcpt); - vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol); + + /* + * An empty original recipient record almost certainly means that + * original recipient processing was disabled. + */ + if (*orig_rcpt) { + quote_822_local(buf, orig_rcpt); + vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol); + } } if (flags & MAIL_COPY_DELIVERED) { if (delivered == 0) diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index f1675e2d3..203f71b47 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -486,6 +486,16 @@ extern char *var_fwd_exp_filter; #define DEF_DELIVER_HDR "command, file, forward" extern char *var_deliver_hdr; + /* + * Cleanup: enable support for X-Original-To message headers, which are + * needed for multi-recipient mailboxes. When this is turned on, perform + * duplicate elimination on (original rcpt, rewritten rcpt) pairs, and + * generating non-empty original recipient records in the queue file. + */ +#define VAR_ENABLE_ORCPT "enable_original_recipient" +#define DEF_ENABLE_ORCPT 1 +extern bool var_enable_orcpt; + #define VAR_EXP_OWN_ALIAS "expand_owner_alias" #define DEF_EXP_OWN_ALIAS 0 extern bool var_exp_own_alias; @@ -763,6 +773,10 @@ extern int var_smtp_rset_tmout; #define DEF_SMTP_QUIT_TMOUT "300s" extern int var_smtp_quit_tmout; +#define VAR_SMTP_QUOTE_821_ENV "smtp_quote_rfc821_envelope" +#define DEF_SMTP_QUOTE_821_ENV 1 +extern int var_smtp_quote_821_env; + #define VAR_SMTP_SKIP_4XX "smtp_skip_4xx_greeting" #define DEF_SMTP_SKIP_4XX 1 extern bool var_smtp_skip_4xx_greeting; @@ -1691,6 +1705,14 @@ extern bool var_verify_neg_cache; #define DEF_VERIFY_SENDER "postmaster" extern char *var_verify_sender; +#define VAR_VERIFY_POLL_COUNT "address_verify_poll_count" +#define DEF_VERIFY_POLL_COUNT 3 +extern int var_verify_poll_count; + +#define VAR_VERIFY_POLL_DELAY "address_verify_poll_delay" +#define DEF_VERIFY_POLL_DELAY "3s" +extern int var_verify_poll_delay; + #define VAR_VRFY_LOCAL_XPORT "address_verify_local_transport" #define DEF_VRFY_LOCAL_XPORT "$" VAR_LOCAL_TRANSPORT extern char *var_vrfy_local_xport; @@ -1804,6 +1826,21 @@ extern char *var_xport_null_key; #define DEF_OLDLOG_COMPAT 1 extern bool var_oldlog_compat; + /* + * SMTPD content proxy. + */ +#define VAR_SMTPD_PROXY_FILT "smtpd_proxy_filter" +#define DEF_SMTPD_PROXY_FILT "" +extern char *var_smtpd_proxy_filt; + +#define VAR_SMTPD_PROXY_EHLO "smtpd_proxy_ehlo" +#define DEF_SMTPD_PROXY_EHLO "$" VAR_MYHOSTNAME +extern char *var_smtpd_proxy_ehlo; + +#define VAR_SMTPD_PROXY_TMOUT "smtpd_proxy_timeout" +#define DEF_SMTPD_PROXY_TMOUT "100s" +extern int var_smtpd_proxy_tmout; + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 7cd711ca6..3f96086e5 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,10 +20,10 @@ * Patches change the patchlevel and the release date. Snapshots change the * release date only, unless they include the same bugfix as a patch release. */ -#define MAIL_RELEASE_DATE "20030621" +#define MAIL_RELEASE_DATE "20030702" #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "2.0.12-" MAIL_RELEASE_DATE +#define DEF_MAIL_VERSION "2.0.13-" MAIL_RELEASE_DATE extern char *var_mail_version; /* diff --git a/postfix/src/global/resolve_clnt.c b/postfix/src/global/resolve_clnt.c index 5892018d2..83df20100 100644 --- a/postfix/src/global/resolve_clnt.c +++ b/postfix/src/global/resolve_clnt.c @@ -66,8 +66,8 @@ /* .IP RESOLVE_CLASS_LOCAL /* The address domain matches $mydestination or $inet_interfaces. /* .IP RESOLVE_CLASS_ALIAS -/* The address domain matches $virtual_alias_domains (simulated -/* virtual domains, where each address is redirected to a real +/* The address domain matches $virtual_alias_domains (virtual +/* alias domains, where each address is redirected to a real /* local or remote address). /* .IP RESOLVE_CLASS_VIRTUAL /* The address domain matches $virtual_mailbox_domains (true diff --git a/postfix/src/global/verify_clnt.h b/postfix/src/global/verify_clnt.h index ebcac033e..40f479033 100644 --- a/postfix/src/global/verify_clnt.h +++ b/postfix/src/global/verify_clnt.h @@ -1,13 +1,13 @@ -#ifndef _VRFY_STAT_H_INCLUDED_ -#define _VRFY_STAT_H_INCLUDED_ +#ifndef _VRFY_CLNT_H_INCLUDED_ +#define _VRFY_CLNT_H_INCLUDED_ /*++ /* NAME -/* mail_proto 3h +/* verify_clnt 3h /* SUMMARY -/* mail internal IPC support +/* address verification client interface /* SYNOPSIS -/* #include +/* #include /* DESCRIPTION /* .nf diff --git a/postfix/src/global/xtext.c b/postfix/src/global/xtext.c index 32a51e499..efbc9671b 100644 --- a/postfix/src/global/xtext.c +++ b/postfix/src/global/xtext.c @@ -2,22 +2,29 @@ /* NAME /* xtext 3 /* SUMMARY -/* translate characters according to RFC 1894 +/* quote/unquote text, HTTP style. /* SYNOPSIS /* #include /* -/* VSTRING *xtext(result, original) -/* VSTRING *result; -/* const char *original; +/* VSTRING *xtext_quote(quoted, unquoted, special) +/* VSTRING *quoted; +/* const char *unquoted; +/* const char *special; +/* +/* VSTRING *xtext_unquote(unquoted, quoted) +/* VSTRING *unquoted; +/* const char *quoted; /* DESCRIPTION -/* xtext() takes a null-terminated string, and produces a translation -/* according to RFC 1894 (DSN). -/* BUGS -/* Cannot replace null characters. +/* xtext_quote() takes a null-terminated string and replaces characters +/* <33(10) and >126(10), as well as characters specified with "special" +/* by +XX, XX being the two-digit uppercase hexadecimal equivalent. /* -/* Does not insert CR LF SPACE to limit output line length. -/* SEE ALSO -/* RFC 1894, Delivery Status Notifications +/* xtext_unquote() performs the opposite transformation. This function +/* understands lowercase, uppercase, and mixed case %XX sequences. The +/* result value is the unquoted argument in case of success, a null pointer +/* otherwise. +/* BUGS +/* This module cannot process null characters in data. /* LICENSE /* .ad /* .fi @@ -31,56 +38,112 @@ /* System library. */ -#include "sys_defs.h" -#include +#include +#include +#include /* Utility library. */ -#include +#include "msg.h" +#include "vstring.h" +#include "xtext.h" -/* Global library. */ +/* Application-specific. */ -#include +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) -/* xtext - translate text according to RFC 1894 */ +/* xtext_quote - unquoted data to quoted */ -VSTRING *xtext(VSTRING *result, const char *original) +VSTRING *xtext_quote(VSTRING *quoted, const char *unquoted, const char *special) { const char *cp; int ch; - /* - * Preliminary implementation. ASCII specific!! - */ - VSTRING_RESET(result); - for (cp = original; (ch = *(unsigned char *) cp) != 0; cp++) { - if (ch == '+' || ch == '\\' || ch == '(' || ch < 33 || ch > 126) - vstring_sprintf_append(result, "+%02X", ch); - else - VSTRING_ADDCH(result, ch); + VSTRING_RESET(quoted); + for (cp = unquoted; (ch = *(unsigned const char *) cp) != 0; cp++) { + if (ch != '+' && ch > 32 && ch < 127 && strchr(special, ch) == 0) { + VSTRING_ADDCH(quoted, ch); + } else { + vstring_sprintf_append(quoted, "+%02X", ch); + } } - VSTRING_TERMINATE(result); + VSTRING_TERMINATE(quoted); + return (quoted); +} + +/* xtext_unquote - quoted data to unquoted */ + +VSTRING *xtext_unquote(VSTRING *unquoted, const char *quoted) +{ + const char *cp; + int ch; - return (result); + VSTRING_RESET(unquoted); + for (cp = quoted; (ch = *cp) != 0; cp++) { + if (ch == '+') { + if (ISDIGIT(cp[1])) + ch = (cp[1] - '0') << 4; + else if (cp[1] >= 'a' && cp[1] <= 'f') + ch = (cp[1] - 'a' + 10) << 4; + else if (cp[1] >= 'A' && cp[1] <= 'F') + ch = (cp[1] - 'A' + 10) << 4; + else + return (0); + if (ISDIGIT(cp[2])) + ch |= (cp[2] - '0'); + else if (cp[2] >= 'a' && cp[2] <= 'f') + ch |= (cp[2] - 'a' + 10); + else if (cp[2] >= 'A' && cp[2] <= 'F') + ch |= (cp[2] - 'A' + 10); + else + return (0); + cp += 2; + } + VSTRING_ADDCH(unquoted, ch); + } + VSTRING_TERMINATE(unquoted); + return (unquoted); } #ifdef TEST -#define STR(x) vstring_str(x) - + /* + * Proof-of-concept test program: convert to quoted and back. + */ #include -int main(int unused_argc, char **unused_argv) +#define BUFLEN 1024 + +static int read_buf(VSTREAM *fp, VSTRING *buf) { - VSTRING *ibuf = vstring_alloc(100); - VSTRING *obuf = vstring_alloc(100); + int len; + + VSTRING_RESET(buf); + len = vstream_fread(fp, STR(buf), vstring_avail(buf)); + VSTRING_AT_OFFSET(buf, len); /* XXX */ + VSTRING_TERMINATE(buf); + return (len); +} - while (vstring_fgets(ibuf, VSTREAM_IN)) { - vstream_fputs(STR(xtext(obuf, STR(ibuf)))); - vstream_fflush(VSTREAM_OUT); +main(int unused_argc, char **unused_argv) +{ + VSTRING *unquoted = vstring_alloc(BUFLEN); + VSTRING *quoted = vstring_alloc(100); + int len; + + while ((len = read_buf(VSTREAM_IN, unquoted)) > 0) { + xtext_quote(quoted, STR(unquoted), "+="); + if (xtext_unquote(unquoted, STR(quoted)) == 0) + msg_fatal("bad input: %.100s", STR(quoted)); + if (LEN(unquoted) != len) + msg_fatal("len %d != unquoted len %d", len, LEN(unquoted)); + if (vstream_fwrite(VSTREAM_OUT, STR(unquoted), LEN(unquoted)) != LEN(unquoted)) + msg_fatal("write error: %m"); } - vstring_free(ibuf); - vstring_free(obuf); + vstream_fflush(VSTREAM_OUT); + vstring_free(unquoted); + vstring_free(quoted); return (0); } diff --git a/postfix/src/global/xtext.h b/postfix/src/global/xtext.h index 3c02057e7..8b924c6bc 100644 --- a/postfix/src/global/xtext.h +++ b/postfix/src/global/xtext.h @@ -1,8 +1,11 @@ +#ifndef _XTEXT_H_INCLUDED_ +#define _XTEXT_H_INCLUDED_ + /*++ /* NAME /* xtext 3h /* SUMMARY -/* translate characters according to RFC 1894 +/* quote/unquote text, xtext style. /* SYNOPSIS /* #include /* DESCRIPTION @@ -10,13 +13,14 @@ /* * Utility library. - */ + */ #include /* * External interface. */ -extern VSTRING *xtext(VSTRING *, const char *); +extern VSTRING *xtext_quote(VSTRING *, const char *, const char *); +extern VSTRING *xtext_unquote(VSTRING *, const char *); /* LICENSE /* .ad @@ -28,3 +32,5 @@ extern VSTRING *xtext(VSTRING *, const char *); /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ + +#endif diff --git a/postfix/src/nqmgr/qmgr_message.c b/postfix/src/nqmgr/qmgr_message.c index 5ac19442d..74b5ab639 100644 --- a/postfix/src/nqmgr/qmgr_message.c +++ b/postfix/src/nqmgr/qmgr_message.c @@ -389,7 +389,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message) if (message->rcpt_offset == 0) { message->rcpt_unread--; qmgr_rcpt_list_add(&message->rcpt_list, curr_offset, - orig_rcpt ? orig_rcpt : "unknown", start); + orig_rcpt ? orig_rcpt : "", start); if (orig_rcpt) { myfree(orig_rcpt); orig_rcpt = 0; diff --git a/postfix/src/postcat/postcat.c b/postfix/src/postcat/postcat.c index 3e19547b5..d45f376ac 100644 --- a/postfix/src/postcat/postcat.c +++ b/postfix/src/postcat/postcat.c @@ -98,7 +98,7 @@ static void postcat(VSTREAM *fp, VSTRING *buffer) * See if this is a plausible file. */ if ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { - if (!strchr(REC_TYPE_POST_ENVELOPE, ch)) { + if (!strchr(REC_TYPE_ENVELOPE, ch)) { msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp)); return; } diff --git a/postfix/src/qmgr/qmgr_message.c b/postfix/src/qmgr/qmgr_message.c index b556179b7..6ed6e62a0 100644 --- a/postfix/src/qmgr/qmgr_message.c +++ b/postfix/src/qmgr/qmgr_message.c @@ -357,7 +357,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message) #define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0)) if (message->rcpt_offset == 0) { qmgr_rcpt_list_add(&message->rcpt_list, curr_offset, - orig_rcpt ? orig_rcpt : "unknown", start); + orig_rcpt ? orig_rcpt : "", start); if (orig_rcpt) { myfree(orig_rcpt); orig_rcpt = 0; diff --git a/postfix/src/showq/showq.c b/postfix/src/showq/showq.c index 59d6ad3cf..4c561a1af 100644 --- a/postfix/src/showq/showq.c +++ b/postfix/src/showq/showq.c @@ -104,7 +104,7 @@ static void showq_reasons(VSTREAM *, BOUNCE_LOG *, HTABLE *); /* showq_report - report status of sender and recipients */ static void showq_report(VSTREAM *client, char *queue, char *id, - VSTREAM *qfile, long size) + VSTREAM *qfile, long size, time_t mtime) { VSTRING *buf = vstring_alloc(100); VSTRING *printable_quoted_addr = vstring_alloc(100); @@ -152,7 +152,8 @@ static void showq_report(VSTREAM *client, char *queue, char *id, printable(STR(printable_quoted_addr), '?'); vstream_fprintf(client, DATA_FORMAT, id, status, msg_size > 0 ? msg_size : size, arrival_time > 0 ? - asctime(localtime(&arrival_time)) : "??", + asctime(localtime(&arrival_time)) : + asctime(localtime(&mtime)), STR(printable_quoted_addr)); break; case REC_TYPE_RCPT: @@ -302,7 +303,8 @@ static void showq_service(VSTREAM *client, char *unused_service, char **argv) vstream_fprintf(client, "\n"); if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) { queue_size += st.st_size; - showq_report(client, qp->name, id, qfile, (long) st.st_size); + showq_report(client, qp->name, id, qfile, (long) st.st_size, + st.st_mtime); if (vstream_fclose(qfile)) msg_warn("close file %s %s: %m", qp->name, id); } else if (strcmp(qp->name, MAIL_QUEUE_MAILDROP) == 0) { diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index f6c0a4c24..531878753 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -21,10 +21,6 @@ /* the destination host, sorts the list by preference, and connects /* to each listed address until it finds a server that responds. /* -/* When the domain or host is specified as a comma/whitespace -/* separated list, the SMTP client repeats the above process -/* for all destinations until it finds a server that responds. -/* /* Once the SMTP client has received the server greeting banner, no /* error will cause it to proceed to the next address on the mail /* exchanger list. Instead, the message is either bounced, or its @@ -118,6 +114,9 @@ /* Some SMTP servers misbehave on long lines. /* .IP \fBsmtp_helo_name\fR /* The hostname to be used in HELO and EHLO commands. +/* .IP \fBsmtp_quote_rfc821_envelope\fR +/* Whether or not to quote MAIL FROM and RCPT TO addresses as +/* per the rules laid out in RFC 821. /* .IP \fBsmtp_skip_4xx_greeting\fR /* Skip servers that greet us with a 4xx status code. /* .IP \fBsmtp_skip_5xx_greeting\fR @@ -298,6 +297,7 @@ int var_smtp_pix_delay; int var_smtp_line_limit; char *var_smtp_helo_name; char *var_smtp_host_lookup; +int var_smtp_quote_821_env; /* * Global variables. smtp_errno is set by the address lookup routines and by @@ -509,6 +509,7 @@ int main(int argc, char **argv) VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo, VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable, VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_SMTP_QUOTE_821_ENV, DEF_SMTP_QUOTE_821_ENV, &var_smtp_quote_821_env, 0, }; diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index b7cc68c05..6cbdb5d3f 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -379,7 +379,7 @@ int smtp_xfer(SMTP_STATE *state) * Macros for readability. */ #define REWRITE_ADDRESS(dst, mid, src) do { \ - if (*(src)) { \ + if (*(src) && var_smtp_quote_821_env) { \ quote_821_local(mid, src); \ smtp_unalias_addr(dst, vstring_str(mid)); \ } else { \ @@ -388,7 +388,7 @@ int smtp_xfer(SMTP_STATE *state) } while (0) #define QUOTE_ADDRESS(dst, src) do { \ - if (*(src)) { \ + if (*(src) && var_smtp_quote_821_env) { \ quote_821_local(dst, src); \ } else { \ vstring_strcpy(dst, src); \ @@ -640,6 +640,7 @@ int smtp_xfer(SMTP_STATE *state) if (resp->code == 552) resp->code = 452; #endif + rcpt = request->rcpt_list.info + recv_rcpt; if (resp->code / 100 == 2) { ++nrcpt; /* If trace-only, mark the recipient done. */ @@ -654,7 +655,6 @@ int smtp_xfer(SMTP_STATE *state) rcpt->offset = 0; /* in case deferred */ } } else { - rcpt = request->rcpt_list.info + recv_rcpt; smtp_rcpt_fail(state, resp->code, rcpt, "host %s said: %s (in reply to %s)", session->namaddr, diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index fbb022397..2f5c64e7d 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -1,10 +1,10 @@ SHELL = /bin/sh SRCS = smtpd.c smtpd_token.c smtpd_check.c smtpd_chat.c smtpd_state.c \ - smtpd_peer.c smtpd_sasl_proto.c smtpd_sasl_glue.c + smtpd_peer.c smtpd_sasl_proto.c smtpd_sasl_glue.c smtpd_proxy.c OBJS = smtpd.o smtpd_token.o smtpd_check.o smtpd_chat.o smtpd_state.o \ - smtpd_peer.o smtpd_sasl_proto.o smtpd_sasl_glue.o + smtpd_peer.o smtpd_sasl_proto.o smtpd_sasl_glue.o smtpd_proxy.o HDRS = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \ - smtpd_sasl_glue.h + smtpd_sasl_glue.h smtpd_proxy.h TESTSRC = smtpd_token_test.c WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ @@ -154,6 +154,7 @@ smtpd.o: smtpd_check.h smtpd.o: smtpd_chat.h smtpd.o: smtpd_sasl_proto.h smtpd.o: smtpd_sasl_glue.h +smtpd.o: smtpd_proxy.h smtpd_chat.o: smtpd_chat.c smtpd_chat.o: ../../include/sys_defs.h smtpd_chat.o: ../../include/msg.h @@ -241,6 +242,25 @@ smtpd_peer.o: smtpd.h smtpd_peer.o: ../../include/vstream.h smtpd_peer.o: ../../include/argv.h smtpd_peer.o: ../../include/mail_stream.h +smtpd_proxy.o: smtpd_proxy.c +smtpd_proxy.o: ../../include/sys_defs.h +smtpd_proxy.o: ../../include/msg.h +smtpd_proxy.o: ../../include/vstream.h +smtpd_proxy.o: ../../include/vbuf.h +smtpd_proxy.o: ../../include/vstring.h +smtpd_proxy.o: ../../include/stringops.h +smtpd_proxy.o: ../../include/connect.h +smtpd_proxy.o: ../../include/iostuff.h +smtpd_proxy.o: ../../include/mail_error.h +smtpd_proxy.o: ../../include/name_mask.h +smtpd_proxy.o: ../../include/smtp_stream.h +smtpd_proxy.o: ../../include/cleanup_user.h +smtpd_proxy.o: ../../include/mail_params.h +smtpd_proxy.o: ../../include/rec_type.h +smtpd_proxy.o: smtpd.h +smtpd_proxy.o: ../../include/argv.h +smtpd_proxy.o: ../../include/mail_stream.h +smtpd_proxy.o: smtpd_proxy.h smtpd_sasl_glue.o: smtpd_sasl_glue.c smtpd_sasl_glue.o: ../../include/sys_defs.h smtpd_sasl_glue.o: ../../include/msg.h diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 054817acc..b3dca183a 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -97,6 +97,24 @@ /* Maps that specify the SASL login name that owns a MAIL FROM sender /* address. Used by the \fBreject_sender_login_mismatch\fR sender /* anti-spoofing restriction. +/* .SH "Pass-through proxy" +/* .ad +/* .fi +/* .ad +/* Optionally, the Postfix SMTP server can be configured to +/* forward all mail to a proxy server, for example a real-time +/* content filter. This proxy server should support the same +/* MAIL FROM and RCPT TO command syntax as Postfix, but does not +/* need to support ESMTP command pipelining. +/* .IP \fBsmtpd_proxy_filter\fR +/* The \fIhost:port\fR of the SMTP proxy server. The \fIhost\fR +/* or \fIhost:\fR portion is optional. +/* .IP \fBsmtpd_proxy_timeout\fR +/* Timeout for connecting to, sending to and receiving from +/* the SMTP proxy server. +/* .IP \fBsmtpd_proxy_ehlo\fR +/* The hostname to use when sending an EHLO command to the +/* SMTP proxy server. /* .SH Miscellaneous /* .ad /* .fi @@ -245,6 +263,21 @@ /* Restrict what domains this mail system will relay /* mail to. The domains are routed to the delivery agent /* specified with the \fBrelay_transport\fR setting. +/* .SH "Sender/recipient address verification" +/* .ad +/* .fi +/* Address verification is implemented by sending probe email +/* messages that are not actually delivered, and is enabled +/* via the reject_unverified_{sender,recipient} access restriction. +/* The status of verification probes is maintained by the address +/* verification service. +/* .IP \fBaddress_verify_poll_count\fR +/* How many times to query the address verification service +/* for completion of an address verification request. +/* Specify 0 to implement a simple form of greylisting. +/* .IP \fBaddress_verify_poll_delay\fR +/* Time to wait after querying the address verification service +/* for completion of an address verification request. /* .SH "UCE control responses" /* .ad /* .fi @@ -289,10 +322,11 @@ /* .IP \fBunverified_recipient_reject_code\fR /* Response code when a recipient address is known to be undeliverable. /* SEE ALSO -/* trivial-rewrite(8) address resolver /* cleanup(8) message canonicalization /* master(8) process manager /* syslogd(8) system logging +/* trivial-rewrite(8) address resolver +/* verify(8) address verification service /* LICENSE /* .ad /* .fi @@ -368,12 +402,13 @@ /* Application-specific */ -#include "smtpd_token.h" -#include "smtpd.h" -#include "smtpd_check.h" -#include "smtpd_chat.h" -#include "smtpd_sasl_proto.h" -#include "smtpd_sasl_glue.h" +#include +#include +#include +#include +#include +#include +#include /* * Tunable parameters. Make sure that there is some bound on the length of @@ -446,6 +481,12 @@ int var_virt_mailbox_code; int var_relay_rcpt_code; char *var_verp_clients; int var_show_unk_rcpt_table; +int var_verify_poll_count; +int var_verify_poll_delay; + +char *var_smtpd_proxy_filt; +int var_smtpd_proxy_tmout; +char *var_smtpd_proxy_ehlo; /* * Silly little macros. @@ -762,7 +803,9 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "503 Error: send HELO/EHLO first"); return (-1); } - if (state->cleanup != 0) { +#define IN_MAIL_TRANSACTION(state) ((state)->cleanup || (state)->proxy) + + if (IN_MAIL_TRANSACTION(state)) { state->error_mask |= MAIL_ERROR_PROTOCOL; smtpd_chat_reply(state, "503 Error: nested MAIL command"); return (-1); @@ -840,50 +883,58 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "%s", err); return (-1); } - if ((err = smtpd_check_size(state, state->msg_size)) != 0) { - smtpd_chat_reply(state, "%s", err); - return (-1); - } + if (SMTPD_STAND_ALONE(state) == 0 && *var_smtpd_proxy_filt) { + if (smtpd_proxy_open(state, var_smtpd_proxy_filt, var_smtpd_proxy_tmout, + var_smtpd_proxy_ehlo, STR(state->buffer)) != 0) { + smtpd_chat_reply(state, "%s", STR(state->proxy_buffer)); + return (-1); + } + } else { + if ((err = smtpd_check_size(state, state->msg_size)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } - /* - * Open queue file or IPC stream. - */ - mail_open_stream(state); + /* + * Open queue file or IPC stream. + */ + mail_open_stream(state); #ifdef USE_SASL_AUTH - if (var_smtpd_sasl_enable) - smtpd_sasl_mail_log(state); - else + if (var_smtpd_sasl_enable) + smtpd_sasl_mail_log(state); + else #endif - msg_info("%s: client=%s[%s]", state->queue_id, state->name, state->addr); - - /* - * Record the time of arrival and the sender envelope address. - */ - if (SMTPD_STAND_ALONE(state) == 0) { - rec_fprintf(state->cleanup, REC_TYPE_TIME, "%ld", - (long) time((time_t *) 0)); - if (*var_filter_xport) - rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); - } - rec_fputs(state->cleanup, REC_TYPE_FROM, argv[2].strval); - if (encoding != 0) - rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", - MAIL_ATTR_ENCODING, encoding); - if (SMTPD_STAND_ALONE(state) == 0) { - rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", - MAIL_ATTR_CLIENT_NAME, state->name); - rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", - MAIL_ATTR_CLIENT_ADDR, state->addr); - rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", - MAIL_ATTR_ORIGIN, state->namaddr); - if (state->helo_name != 0) + msg_info("%s: client=%s[%s]", state->queue_id, state->name, state->addr); + + /* + * Record the time of arrival and the sender envelope address. + */ + if (SMTPD_STAND_ALONE(state) == 0) { + rec_fprintf(state->cleanup, REC_TYPE_TIME, "%ld", + (long) time((time_t *) 0)); + if (*var_filter_xport) + rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); + } + rec_fputs(state->cleanup, REC_TYPE_FROM, argv[2].strval); + if (encoding != 0) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ENCODING, encoding); + if (SMTPD_STAND_ALONE(state) == 0) { rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", - MAIL_ATTR_HELO_NAME, state->helo_name); - rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", - MAIL_ATTR_PROTO_NAME, state->protocol); + MAIL_ATTR_CLIENT_NAME, state->name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_CLIENT_ADDR, state->addr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ORIGIN, state->namaddr); + if (state->helo_name != 0) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_HELO_NAME, state->helo_name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_PROTO_NAME, state->protocol); + } + if (verp_delims) + rec_fputs(state->cleanup, REC_TYPE_VERP, verp_delims); } - if (verp_delims) - rec_fputs(state->cleanup, REC_TYPE_VERP, verp_delims); state->sender = mystrdup(argv[2].strval); smtpd_chat_reply(state, "250 Ok"); return (0); @@ -918,6 +969,8 @@ static void mail_reset(SMTPD_STATE *state) smtpd_sasl_mail_reset(state); #endif state->discard = 0; + if (state->proxy) + smtpd_proxy_close(state); } /* rcpt_cmd - process RCPT TO command */ @@ -937,7 +990,7 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) * command with a 501 response. So much for the principle of "be liberal * in what you accept, be strict in what you send". */ - if (state->cleanup == 0) { + if (!IN_MAIL_TRANSACTION(state)) { state->error_mask |= MAIL_ERROR_PROTOCOL; smtpd_chat_reply(state, "503 Error: need MAIL command"); return (-1); @@ -977,6 +1030,11 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) return (-1); } } + if (state->proxy && smtpd_proxy_cmd(state, SMTPD_PROX_STAT_OK, + "%s", STR(state->buffer)) != 0) { + smtpd_chat_reply(state, "%s", STR(state->proxy_buffer)); + return (-1); + } /* * Store the recipient. Remember the first one. @@ -984,7 +1042,8 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) state->rcpt_count++; if (state->recipient == 0) state->recipient = mystrdup(argv[2].strval); - rec_fputs(state->cleanup, REC_TYPE_RCPT, argv[2].strval); + if (state->cleanup) + rec_fputs(state->cleanup, REC_TYPE_RCPT, argv[2].strval); smtpd_chat_reply(state, "250 Ok"); return (0); } @@ -1012,6 +1071,10 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) int first = 1; VSTRING *why = 0; int saved_err; + int (*out_record) (VSTREAM *, int, const char *, int); + int (*out_fprintf) (VSTREAM *, int, const char *,...); + VSTREAM *out_stream; + int out_error; /* * Sanity checks. With ESMTP command pipelining the client can send DATA @@ -1019,7 +1082,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) * error. */ if (state->rcpt_count == 0) { - if (state->cleanup == 0) { + if (!IN_MAIL_TRANSACTION(state)) { state->error_mask |= MAIL_ERROR_PROTOCOL; smtpd_chat_reply(state, "503 Error: need RCPT command"); } else { @@ -1036,35 +1099,61 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) smtpd_chat_reply(state, "%s", err); return (-1); } + if (state->proxy && smtpd_proxy_cmd(state, SMTPD_PROX_STAT_MORE, + "%s", STR(state->buffer)) != 0) { + smtpd_chat_reply(state, "%s", STR(state->proxy_buffer)); + return (-1); + } + + /* + * One level of indirection to choose between normal or proxied + * operation. We want to avoid massive code duplication within tons of + * if-else clauses. + */ + if (state->proxy) { + out_stream = state->proxy; + out_record = smtpd_proxy_rec_put; + out_fprintf = smtpd_proxy_rec_fprintf; + out_error = CLEANUP_STAT_PROXY; + } else { + out_stream = state->cleanup; + out_record = rec_put; + out_fprintf = rec_fprintf; + out_error = CLEANUP_STAT_WRITE; + } /* * Terminate the message envelope segment. Start the message content * segment, and prepend our own Received: header. If there is only one * recipient, list the recipient address. */ - rec_fputs(state->cleanup, REC_TYPE_MESG, ""); - rec_fprintf(state->cleanup, REC_TYPE_NORM, + if (state->cleanup) + rec_fputs(state->cleanup, REC_TYPE_MESG, ""); + out_fprintf(out_stream, REC_TYPE_NORM, "Received: from %s (%s [%s])", state->helo_name ? state->helo_name : state->name, state->name, state->addr); if (state->rcpt_count == 1 && state->recipient) { - rec_fprintf(state->cleanup, REC_TYPE_NORM, - "\tby %s (%s) with %s id %s", + out_fprintf(out_stream, REC_TYPE_NORM, + state->cleanup ? "\tby %s (%s) with %s id %s" : + "\tby %s (%s) with %s", var_myhostname, var_mail_name, state->protocol, state->queue_id); quote_822_local(state->buffer, state->recipient); - rec_fprintf(state->cleanup, REC_TYPE_NORM, + out_fprintf(out_stream, REC_TYPE_NORM, "\tfor <%s>; %s", STR(state->buffer), mail_date(state->time)); } else { - rec_fprintf(state->cleanup, REC_TYPE_NORM, - "\tby %s (%s) with %s", - var_myhostname, var_mail_name, state->protocol); - rec_fprintf(state->cleanup, REC_TYPE_NORM, - "\tid %s; %s", state->queue_id, mail_date(state->time)); + out_fprintf(out_stream, REC_TYPE_NORM, + state->cleanup ? "\tby %s (%s) with %s id %s;" : + "\tby %s (%s) with %s;", + var_myhostname, var_mail_name, + state->protocol, state->queue_id); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t%s", mail_date(state->time)); } #ifdef RECEIVED_ENVELOPE_FROM quote_822_local(state->buffer, state->sender); - rec_fprintf(state->cleanup, REC_TYPE_NORM, + out_fprintf(out_stream, REC_TYPE_NORM, "\t(envelope-from %s)", STR(state->buffer)); #endif smtpd_chat_reply(state, "354 End data with ."); @@ -1081,9 +1170,6 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) * XXX Deal with UNIX-style From_ lines at the start of message content * because sendmail permits it. */ - if (vstream_fflush(state->cleanup)) - state->err = CLEANUP_STAT_WRITE; - for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type) { if (smtp_get(state->buffer, state->client, var_line_limit) == '\n') curr_rec_type = REC_TYPE_NORM; @@ -1093,40 +1179,46 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) len = VSTRING_LEN(state->buffer); if (first) { if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) { - rec_fprintf(state->cleanup, curr_rec_type, + out_fprintf(out_stream, curr_rec_type, "X-Mailbox-Line: %s", start); continue; } first = 0; if (len > 0 && IS_SPACE_TAB(start[0])) - rec_put(state->cleanup, REC_TYPE_NORM, "", 0); + out_record(out_stream, REC_TYPE_NORM, "", 0); } - if (prev_rec_type != REC_TYPE_CONT - && *start == '.' && (++start, --len) == 0) + if (prev_rec_type != REC_TYPE_CONT && *start == '.' + && (state->proxy == 0 ? (++start, --len) == 0 : len == 1)) break; if (state->err == CLEANUP_STAT_OK - && rec_put(state->cleanup, curr_rec_type, start, len) < 0) - state->err = CLEANUP_STAT_WRITE; + && out_record(out_stream, curr_rec_type, start, len) < 0) + state->err = out_error; } /* * Send the end-of-segment markers. */ - if (state->err == CLEANUP_STAT_OK) - if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0 - || rec_fputs(state->cleanup, REC_TYPE_END, "") < 0 - || vstream_fflush(state->cleanup)) - state->err = CLEANUP_STAT_WRITE; - - /* - * Finish the queue file or finish the cleanup conversation. - */ - if (state->err == 0) - state->err = mail_stream_finish(state->dest, why = vstring_alloc(10)); - else - mail_stream_cleanup(state->dest); - state->dest = 0; - state->cleanup = 0; + if (state->proxy) { + if (state->err == CLEANUP_STAT_OK) + (void) smtpd_proxy_cmd(state, SMTPD_PROX_STAT_ANY, "."); + smtpd_proxy_close(state); + } else { + if (state->err == CLEANUP_STAT_OK) + if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0 + || rec_fputs(state->cleanup, REC_TYPE_END, "") < 0 + || vstream_fflush(state->cleanup)) + state->err = CLEANUP_STAT_WRITE; + + /* + * Finish the queue file or finish the cleanup conversation. + */ + if (state->err == 0) + state->err = mail_stream_finish(state->dest, why = vstring_alloc(10)); + else + mail_stream_cleanup(state->dest); + state->dest = 0; + state->cleanup = 0; + } /* * Handle any errors. One message may suffer from multiple errors, so @@ -1139,7 +1231,10 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) state->error_count = 0; state->error_mask = 0; state->junk_cmds = 0; - smtpd_chat_reply(state, "250 Ok: queued as %s", state->queue_id); + if (state->queue_id) + smtpd_chat_reply(state, "250 Ok: queued as %s", state->queue_id); + else + smtpd_chat_reply(state, "%s", STR(state->proxy_buffer)); } else if ((state->err & CLEANUP_STAT_BAD) != 0) { state->error_mask |= MAIL_ERROR_SOFTWARE; smtpd_chat_reply(state, "451 Error: internal error %d", state->err); @@ -1156,6 +1251,9 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) } else if ((state->err & CLEANUP_STAT_WRITE) != 0) { state->error_mask |= MAIL_ERROR_RESOURCE; smtpd_chat_reply(state, "451 Error: queue file write error"); + } else if ((state->err & CLEANUP_STAT_PROXY) != 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + smtpd_chat_reply(state, "451 Error: queue file write error"); } else { state->error_mask |= MAIL_ERROR_SOFTWARE; smtpd_chat_reply(state, "451 Error: internal error %d", state->err); @@ -1310,7 +1408,7 @@ static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "503 Error: send HELO/EHLO first"); return (-1); } - if (state->cleanup != 0) { + if (IN_MAIL_TRANSACTION(state)) { state->error_mask |= MAIL_ERROR_PROTOCOL; smtpd_chat_reply(state, "503 Error: MAIL transaction in progress"); return (-1); @@ -1409,7 +1507,7 @@ typedef struct SMTPD_CMD { } SMTPD_CMD; #define SMTPD_CMD_FLAG_LIMIT (1<<0) /* limit usage */ -#define SMTPD_CMD_FLAG_HEADER (1<<1) /* RFC 2822 mail header */ +#define SMTPD_CMD_FLAG_FORBIDDEN (1<<1) /* RFC 2822 mail header */ static SMTPD_CMD smtpd_cmd_table[] = { "HELO", helo_cmd, SMTPD_CMD_FLAG_LIMIT, @@ -1427,11 +1525,13 @@ static SMTPD_CMD smtpd_cmd_table[] = { "VRFY", vrfy_cmd, SMTPD_CMD_FLAG_LIMIT, "ETRN", etrn_cmd, SMTPD_CMD_FLAG_LIMIT, "QUIT", quit_cmd, 0, - "Received:", 0, SMTPD_CMD_FLAG_HEADER, - "Reply-To:", 0, SMTPD_CMD_FLAG_HEADER, - "Message-ID:", 0, SMTPD_CMD_FLAG_HEADER, - "Subject:", 0, SMTPD_CMD_FLAG_HEADER, - "From:", 0, SMTPD_CMD_FLAG_HEADER, + "Received:", 0, SMTPD_CMD_FLAG_FORBIDDEN, + "Reply-To:", 0, SMTPD_CMD_FLAG_FORBIDDEN, + "Message-ID:", 0, SMTPD_CMD_FLAG_FORBIDDEN, + "Subject:", 0, SMTPD_CMD_FLAG_FORBIDDEN, + "From:", 0, SMTPD_CMD_FLAG_FORBIDDEN, + "CONNECT", 0, SMTPD_CMD_FLAG_FORBIDDEN, + "User-Agent:", 0, SMTPD_CMD_FLAG_FORBIDDEN, 0, }; @@ -1521,8 +1621,8 @@ static void smtpd_proto(SMTPD_STATE *state) state->error_count++; continue; } - if (cmdp->flags & SMTPD_CMD_FLAG_HEADER) { - msg_warn("%s sent %s header instead of SMTP command: %.100s", + if (cmdp->flags & SMTPD_CMD_FLAG_FORBIDDEN) { + msg_warn("%s sent %s instead of SMTP command: %.100s", state->namaddr, cmdp->name, vstring_str(state->buffer)); smtpd_chat_reply(state, "221 Error: I can break rules, too. Goodbye."); break; @@ -1685,11 +1785,14 @@ int main(int argc, char **argv) VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, 0, 0, VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, 0, 0, VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, 0, 0, + VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count, 1, 0, 0, }; static CONFIG_TIME_TABLE time_table[] = { VAR_SMTPD_TMOUT, DEF_SMTPD_TMOUT, &var_smtpd_tmout, 1, 0, VAR_SMTPD_ERR_SLEEP, DEF_SMTPD_ERR_SLEEP, &var_smtpd_err_sleep, 0, 0, + VAR_SMTPD_PROXY_TMOUT, DEF_SMTPD_PROXY_TMOUT, &var_smtpd_proxy_tmout, 1, 0, + VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay, 1, 0, 0, }; static CONFIG_BOOL_TABLE bool_table[] = { @@ -1732,6 +1835,8 @@ int main(int argc, char **argv) VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0, VAR_VERP_CLIENTS, DEF_VERP_CLIENTS, &var_verp_clients, 0, 0, + VAR_SMTPD_PROXY_FILT, DEF_SMTPD_PROXY_FILT, &var_smtpd_proxy_filt, 0, 0, + VAR_SMTPD_PROXY_EHLO, DEF_SMTPD_PROXY_EHLO, &var_smtpd_proxy_ehlo, 0, 0, 0, }; static CONFIG_RAW_TABLE raw_table[] = { diff --git a/postfix/src/smtpd/smtpd.h b/postfix/src/smtpd/smtpd.h index a369b7e86..bc825be1e 100644 --- a/postfix/src/smtpd/smtpd.h +++ b/postfix/src/smtpd/smtpd.h @@ -95,6 +95,8 @@ typedef struct SMTPD_STATE { int defer_if_permit_sender; /* force permit into warning */ int discard; /* discard message */ VSTRING *expand_buf; /* scratch space for $name expansion */ + VSTREAM *proxy; /* proxy handle */ + VSTRING *proxy_buffer; /* proxy query/reply buffer */ } SMTPD_STATE; extern void smtpd_state_init(SMTPD_STATE *, VSTREAM *); diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 1bb029133..54ce51789 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -1153,8 +1153,9 @@ static int check_relay_domains(SMTPD_STATE *state, char *recipient, if (once == 0) { once = 1; - msg_warn("the \"%s\" restriction is going away; use \"%s\" instead", - CHECK_RELAY_DOMAINS, REJECT_UNAUTH_DEST); + msg_warn("support for restriction \"%s\" will be removed from %s; " + "use \"%s\" instead", + CHECK_RELAY_DOMAINS, var_mail_name, REJECT_UNAUTH_DEST); } #endif @@ -1650,17 +1651,17 @@ static int reject_unverified_address(SMTPD_STATE *state, const char *addr, /* * Verify the address. Don't waste too much of their or our time. */ - for (count = 0; /* see below */ ; count++) { + for (count = 0; /* see below */ ; /* see below */ ) { verify_status = verify_clnt_query(addr, &rcpt_status, why); if (verify_status != VRFY_STAT_OK || rcpt_status != DEL_RCPT_STAT_TODO) break; - if (count >= 2) + if (++count >= var_verify_poll_count) break; - sleep(3); + sleep(var_verify_poll_delay); } if (verify_status != VRFY_STAT_OK) { msg_warn("%s service failure", var_verify_service); - DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY, + DEFER_IF_PERMIT2(state, MAIL_ERROR_POLICY, "450 <%s>: %s rejected: address verification problem", reply_name, reply_class); rqst_status = SMTPD_CHECK_DUNNO; @@ -2589,8 +2590,9 @@ static int reject_maps_rbl(SMTPD_STATE *state) if (warned == 0) { warned++; - msg_warn("restriction %s is going away. Please use %s instead", - REJECT_MAPS_RBL, REJECT_RBL_CLIENT); + msg_warn("support for restriction \"%s\" will be removed from %s; " + "use \"%s \" instead", + REJECT_MAPS_RBL, var_mail_name, REJECT_RBL_CLIENT); } while ((rbl_domain = mystrtok(&bp, " \t\r\n,")) != 0) { result = reject_rbl_addr(state, rbl_domain, state->addr, @@ -2683,7 +2685,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, int saved_recursion = state->recursion++; if (msg_verbose) - msg_info("%s: START", myname); + msg_info(">>> START %s RESTRICTIONS <<<", reply_class); for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) { @@ -2980,7 +2982,7 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, break; } if (msg_verbose && name == 0) - msg_info("%s: END", myname); + msg_info(">>> END %s RESTRICTIONS <<<", reply_class); state->recursion = saved_recursion; @@ -3296,6 +3298,9 @@ static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient) return (0); state->rcptmap_checked = 1; + if (msg_verbose) + msg_info(">>> CHECKING RECIPIENT MAPS <<<"); + /* * Resolve the address. */ @@ -3653,6 +3658,8 @@ int var_relay_rcpt_code; int var_virt_mailbox_code; int var_virt_alias_code; int var_show_unk_rcpt_table; +int var_verify_poll_count; +int var_verify_poll_delay; static INT_TABLE int_table[] = { "msg_verbose", 0, &msg_verbose, @@ -3676,6 +3683,8 @@ static INT_TABLE int_table[] = { VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table, + VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count, + VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay, 0, }; @@ -3803,6 +3812,14 @@ int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot) #endif +/* verify_clnt_query - stub */ + +int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why) +{ + *addr_status = DEL_RCPT_STAT_OK; + return (VRFY_STAT_OK); +} + /* canon_addr_internal - stub */ VSTRING *canon_addr_internal(VSTRING *result, const char *addr) @@ -3816,7 +3833,7 @@ VSTRING *canon_addr_internal(VSTRING *result, const char *addr) /* resolve_clnt_query - stub */ -void resolve_clnt_query(const char *addr, RESOLVE_REPLY *reply) +void resolve_clnt(const char *class, const char *addr, RESOLVE_REPLY *reply) { const char *domain; diff --git a/postfix/src/smtpd/smtpd_proxy.c b/postfix/src/smtpd/smtpd_proxy.c new file mode 100644 index 000000000..ffe998b61 --- /dev/null +++ b/postfix/src/smtpd/smtpd_proxy.c @@ -0,0 +1,437 @@ +/*++ +/* NAME +/* smtpd_proto 3 +/* SUMMARY +/* SMTP server pass-through proxy client +/* SYNOPSIS +/* #include +/* #include +/* +/* typedef struct { +/* .in +4 +/* /* other fields... */ +/* VSTREAM *proxy; /* connection to SMTP proxy */ +/* VSTRING *proxy_reply; /* last SMTP proxy response */ +/* /* other fields... */ +/* .in -4 +/* } SMTPD_STATE; +/* +/* int smtpd_proxy_open(state, service, timeout, ehlo_name, mail_from) +/* SMTPD_STATE *state; +/* const char *service; +/* int timeout; +/* const char *ehlo_name; +/* const char *mail_from; +/* +/* int smtpd_proxy_cmd(state, expect, format, ...) +/* SMTPD_STATE *state; +/* int expect; +/* cont char *format; +/* +/* void smtpd_proxy_open(state) +/* SMTPD_STATE *state; +/* RECORD-LEVEL ROUTINES +/* int smtpd_proxy_rec_put(stream, rec_type, data, len) +/* VSTREAM *stream; +/* int rec_type; +/* const char *data; +/* int len; +/* +/* int smtpd_proxy_rec_fprintf(stream, rec_type, format, ...) +/* VSTREAM *stream; +/* int rec_type; +/* cont char *format; +/* DESCRIPTION +/* The functions in this module implement a pass-through proxy +/* client. +/* +/* In order to minimize the intrusiveness of pass-through proxying, 1) the +/* proxy server must support the same MAIL FROM/RCPT syntax that Postfix +/* supports, 2) the record-level routines for message content proxying +/* have the same interface as the routines that are used for non-proxied +/* mail. +/* +/* smtpd_proxy_open() should be called after receiving the MAIL FROM +/* command. It connects to the proxy service, sends EHLO, sends the +/* MAIL FROM command, and receives the reply. A non-zero result means +/* trouble: either the proxy is unavailable, or it did not send the +/* expected reply. +/* All results are reported via the state->proxy_reply field in a form +/* that can be sent to the SMTP client. In case of error, the +/* state->error_mask and state->err fields are updated. +/* A state->proxy_reply field is created automatically; this field +/* persists beyond the end of a proxy session. +/* +/* smtpd_proxy_cmd() formats and sends the specified command to the +/* proxy server, and receives the proxy server reply. A non-zero result +/* means trouble: either the proxy is unavailable, or it did not send the +/* expected reply. +/* All results are reported via the state->proxy_reply field in a form +/* that can be sent to the SMTP client. In case of error, the +/* state->error_mask and state->err fields are updated. +/* +/* smtpd_proxy_close() disconnects from a proxy server and resets +/* the state->proxy field. The last proxy server reply or error +/* description remains available via state->proxy-reply. +/* +/* smtpd_proxy_rec_put() is a rec_put() clone that passes arbitrary +/* message content records to the proxy server. The data is expected +/* to be in SMTP dot-escaped form. All errors are reported as a +/* REC_TYPE_ERROR result value. +/* +/* smtpd_proxy_rec_fprintf() is a rec_fprintf() clone that formats +/* message content and sends it to the proxy server. Leading dots are +/* not escaped. All errors are reported as a REC_TYPE_ERROR result +/* value. +/* +/* Arguments: +/* .IP server +/* The SMTP proxy server host:port. The host or host: part is optional. +/* .IP timeout +/* Time limit for connecting to the proxy server and for +/* sending and receiving proxy server commands and replies. +/* .IP ehlo_name +/* The EHLO Hostname that will be sent to the proxy server. +/* .IP mail_from +/* The MAIL FROM command. +/* .IP state +/* SMTP server state. +/* .IP expect +/* Expected proxy server reply status code range. A warning is logged +/* when an unexpected reply is received. Specify one of the following: +/* .RS +/* .IP SMTPD_PROX_STAT_ANY +/* The caller has no expectation. Do not warn for unexpected replies. +/* .IP SMTPD_PROX_STAT_OK +/* The caller expects a reply in the 200 range. +/* .IP SMTPD_PROX_STAT_MORE +/* The caller expects a reply in the 300 range. +/* .IP SMTPD_PROX_STAT_DEFER +/* .IP SMTPD_PROX_STAT_FAIL +/* The caller perversely expects a reply in the 400 and 500 range, +/* respectively. +/* .RE +/* .IP format +/* A format string. +/* .IP stream +/* Connection to proxy server. +/* .IP data +/* Pointer to the content of one message content record. +/* .IP len +/* The length of a message content record. +/* SEE ALSO +/* smtpd(8) Postfix smtp server +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* +/* Warnings: unexpected response from proxy server, unable +/* to connect to proxy server, proxy server read/write error. +/* 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 +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include +#include + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* smtpd_proxy_open - open proxy connection after MAIL FROM */ + +int smtpd_proxy_open(SMTPD_STATE *state, const char *service, + int timeout, const char *ehlo_name, + const char *mail_from) +{ + int fd; + + /* + * This buffer persists beyond the end of a proxy session so we can + * inspect the last command's reply. + */ + if (state->proxy_buffer == 0) + state->proxy_buffer = vstring_alloc(10); + + /* + * Connect to proxy. + */ + if ((fd = inet_connect(service, BLOCKING, timeout)) < 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + state->err |= CLEANUP_STAT_PROXY; + msg_warn("connect to proxy service %s: %m", service); + vstring_sprintf(state->proxy_buffer, + "451 Error: queue file write error"); + return (-1); + } + state->proxy = vstream_fdopen(fd, O_RDWR); + vstream_control(state->proxy, VSTREAM_CTL_PATH, service, VSTREAM_CTL_END); + smtp_timeout_setup(state->proxy, timeout); + + /* + * Get server greeting banner. + * + * XXX If this fails then we should not send the initial reply when the + * client expects the MAIL FROM reply. + */ + if (smtpd_proxy_cmd(state, SMTPD_PROX_STAT_OK, (char *) 0) != 0) { + vstring_sprintf(state->proxy_buffer, + "451 Error: queue file write error"); + smtpd_proxy_close(state); + return (-1); + } + + /* + * Send our own EHLO command. + * + * XXX If this fails then we should not send the EHLO reply when the client + * expects the MAIL FROM reply. + */ + if (smtpd_proxy_cmd(state, SMTPD_PROX_STAT_OK, "EHLO %s", ehlo_name) != 0) { + vstring_sprintf(state->proxy_buffer, + "451 Error: queue file write error"); + smtpd_proxy_close(state); + return (-1); + } + + /* + * Pass-through the client's MAIL FROM command. + */ + if (smtpd_proxy_cmd(state, SMTPD_PROX_STAT_OK, "%s", mail_from) != 0) { + smtpd_proxy_close(state); + return (-1); + } + return (0); +} + +/* smtpd_proxy_comms_error - report proxy communication error */ + +static int smtpd_proxy_comms_error(VSTREAM *stream, int err) +{ + switch (err) { + case SMTP_ERR_EOF: + msg_warn("lost connection with proxy %s", VSTREAM_PATH(stream)); + return (err); + case SMTP_ERR_TIME: + msg_warn("timeout talking to proxy %s", VSTREAM_PATH(stream)); + return (err); + default: + msg_panic("smtpd_proxy_comms_error: unknown proxy %s stream error %d", + VSTREAM_PATH(stream), err); + } +} + +/* smtpd_proxy_cmd_error - report unexpected proxy reply */ + +static void smtpd_proxy_cmd_error(SMTPD_STATE *state, const char *fmt, + va_list ap) +{ + VSTRING *buf; + + /* + * The command can be omitted at the start of an SMTP session. A null + * format string is not documented as part of the official interface + * because it is used only internally to this module. + */ + buf = vstring_alloc(100); + vstring_vsprintf(buf, fmt && *fmt ? fmt : "connection request", ap); + msg_warn("proxy %s rejected \"%s\": \"%s\"", VSTREAM_PATH(state->proxy), + STR(buf), STR(state->proxy_buffer)); + vstring_free(buf); +} + +/* smtpd_proxy_cmd - send command to proxy, receive reply */ + +int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) +{ + va_list ap; + char *cp; + int last_char; + int err = 0; + + /* + * Errors first. Be prepared for delayed errors from the DATA phase. + */ + if (vstream_ftimeout(state->proxy) + || vstream_ferror(state->proxy) + || vstream_feof(state->proxy) + || ((err = vstream_setjmp(state->proxy) != 0) + && smtpd_proxy_comms_error(state->proxy, err))) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + state->err |= CLEANUP_STAT_PROXY; + vstring_sprintf(state->proxy_buffer, + "451 Error: queue file write error"); + return (-1); + } + + /* + * The command can be omitted at the start of an SMTP session. A null + * format string is not documented as part of the official interface + * because it is used only internally to this module. + */ + if (fmt && *fmt) { + + /* + * Format the command. + */ + va_start(ap, fmt); + vstring_vsprintf(state->proxy_buffer, fmt, ap); + va_end(ap); + + /* + * Optionally log the command first, so that we can see in the log + * what the program is trying to do. + */ + if (msg_verbose) + msg_info("> %s: %s", VSTREAM_PATH(state->proxy), + STR(state->proxy_buffer)); + + /* + * Send the command to the proxy server. Since we're going to read a + * reply immediately, there is no need to flush buffers. + */ + smtp_fputs(STR(state->proxy_buffer), LEN(state->proxy_buffer), + state->proxy); + } + + /* + * Censor out non-printable characters in server responses and keep the + * last line of multi-line responses. + */ + for (;;) { + last_char = smtp_get(state->proxy_buffer, state->proxy, var_line_limit); + printable(STR(state->proxy_buffer), '?'); + if (last_char != '\n') + msg_warn("%s: response longer than %d: %.30s...", + VSTREAM_PATH(state->proxy), var_line_limit, + STR(state->proxy_buffer)); + if (msg_verbose) + msg_info("< %s: %s", VSTREAM_PATH(state->proxy), + STR(state->proxy_buffer)); + + /* + * Parse the response into code and text. Ignore unrecognized + * garbage. This means that any character except space (or end of + * line) will have the same effect as the '-' line continuation + * character. + */ + for (cp = STR(state->proxy_buffer); *cp && ISDIGIT(*cp); cp++) + /* void */ ; + if (cp - STR(state->proxy_buffer) == 3) { + if (*cp == '-') + continue; + if (*cp == ' ' || *cp == 0) + break; + } + msg_warn("received garbage from proxy %s: %.100s", + VSTREAM_PATH(state->proxy), STR(state->proxy_buffer)); + } + + /* + * Log a warning in case the proxy does not send the expected response. + * Silently accept any response when the client expressed no expectation. + */ + if (expect != SMTPD_PROX_STAT_ANY + && expect != (STR(state->proxy_buffer)[0] - '0')) { + va_start(ap, fmt); + smtpd_proxy_cmd_error(state, fmt, ap); + va_end(ap); + return (-1); + } else { + return (0); + } +} + +/* smtpd_proxy_rec_put - send message content, rec_put() clone */ + +int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type, + const char *data, int len) +{ + int err; + + /* + * Errors first. + */ + if (vstream_ftimeout(stream) || vstream_ferror(stream) + || vstream_feof(stream)) + return (REC_TYPE_ERROR); + if ((err = vstream_setjmp(stream)) != 0) + return (smtpd_proxy_comms_error(stream, err), REC_TYPE_ERROR); + + /* + * Send one content record. Errors and results must be as with rec_put(). + */ + if (rec_type == REC_TYPE_NORM) + smtp_fputs(data, len, stream); + else + smtp_fwrite(data, len, stream); + return (rec_type); +} + +/* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */ + +int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type, + const char *fmt,...) +{ + va_list ap; + int err; + + /* + * Errors first. + */ + if (vstream_ftimeout(stream) || vstream_ferror(stream) + || vstream_feof(stream)) + return (REC_TYPE_ERROR); + if ((err = vstream_setjmp(stream)) != 0) + return (smtpd_proxy_comms_error(stream, err), REC_TYPE_ERROR); + + /* + * Send one content record. Errors and results must be as with + * rec_fprintf(). + */ + va_start(ap, fmt); + if (rec_type != REC_TYPE_NORM) + msg_panic("smtpd_proxy_rec_fprintf: need REC_TYPE_NORM"); + smtp_vprintf(stream, fmt, ap); + va_end(ap); + return (rec_type); +} + +/* smtpd_proxy_close - close proxy connection */ + +void smtpd_proxy_close(SMTPD_STATE *state) +{ + (void) vstream_fclose(state->proxy); + state->proxy = 0; +} diff --git a/postfix/src/smtpd/smtpd_proxy.h b/postfix/src/smtpd/smtpd_proxy.h new file mode 100644 index 000000000..9d0fe53d9 --- /dev/null +++ b/postfix/src/smtpd/smtpd_proxy.h @@ -0,0 +1,42 @@ +/*++ +/* NAME +/* smtpd_proxy 3h +/* SUMMARY +/* SMTP server pass-through proxy client +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Application-specific. + */ +#define SMTPD_PROX_STAT_ANY 0 +#define SMTPD_PROX_STAT_OK 2 +#define SMTPD_PROX_STAT_MORE 3 +#define SMTPD_PROX_STAT_DEFER 4 +#define SMTPD_PROX_STAT_FAIL 5 + +extern int smtpd_proxy_open(SMTPD_STATE *, const char *, int, const char *, const char *); +extern int smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...); +extern int smtpd_proxy_rec_put(VSTREAM *, int, const char *, int); +extern int smtpd_proxy_rec_fprintf(VSTREAM *, int, const char *,...); +extern void smtpd_proxy_close(SMTPD_STATE *); + +/* 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 +/*--*/ diff --git a/postfix/src/smtpd/smtpd_state.c b/postfix/src/smtpd/smtpd_state.c index 6475ae49b..f0b148d55 100644 --- a/postfix/src/smtpd/smtpd_state.c +++ b/postfix/src/smtpd/smtpd_state.c @@ -99,6 +99,8 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream) state->defer_if_permit.reason = 0; state->discard = 0; state->expand_buf = 0; + state->proxy = 0; + state->proxy_buffer = 0; #ifdef USE_SASL_AUTH if (SMTPD_STAND_ALONE(state)) @@ -137,6 +139,8 @@ void smtpd_state_reset(SMTPD_STATE *state) vstring_free(state->defer_if_reject.reason); if (state->expand_buf) vstring_free(state->expand_buf); + if (state->proxy_buffer) + vstring_free(state->proxy_buffer); #ifdef USE_SASL_AUTH if (var_smtpd_sasl_enable) diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 19c863f43..74b538783 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -3,7 +3,7 @@ SRCS = alldig.c argv.c argv_split.c attr_print0.c attr_print64.c \ attr_scan0.c attr_scan64.c base64_code.c basename.c binhash.c \ chroot_uid.c clean_env.c close_on_exec.c concatenate.c ctable.c \ dict.c dict_alloc.c dict_db.c dict_dbm.c dict_debug.c dict_env.c \ - dict_ht.c dict_ldap.c dict_mysql.c dict_ni.c dict_nis.c \ + dict_cidr.c dict_ht.c dict_ldap.c dict_mysql.c dict_ni.c dict_nis.c \ dict_nisplus.c dict_open.c dict_pcre.c dict_pgsql.c dict_regexp.c \ dict_static.c dict_tcp.c dict_unix.c dir_forest.c doze.c \ duplex_pipe.c environ.c events.c exec_command.c fifo_listen.c \ @@ -31,7 +31,7 @@ OBJS = alldig.o argv.o argv_split.o attr_print0.o attr_print64.o \ attr_scan0.o attr_scan64.o base64_code.o basename.o binhash.o \ chroot_uid.o clean_env.o close_on_exec.o concatenate.o ctable.o \ dict.o dict_alloc.o dict_db.o dict_dbm.o dict_debug.o dict_env.o \ - dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \ + dict_cidr.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \ dict_nisplus.o dict_open.o dict_pcre.o dict_pgsql.o dict_regexp.o \ dict_static.o dict_tcp.o dict_unix.o dir_forest.o doze.o \ duplex_pipe.o environ.o events.o exec_command.o fifo_listen.o \ @@ -57,7 +57,7 @@ OBJS = alldig.o argv.o argv_split.o attr_print0.o attr_print64.o \ write_buf.o write_wait.o $(STRCASE) HDRS = argv.h attr.h base64_code.h binhash.h chroot_uid.h clean_env.h \ connect.h ctable.h dict.h dict_db.h dict_dbm.h dict_env.h \ - dict_ht.h dict_ldap.h dict_mysql.h dict_ni.h dict_nis.h \ + dict_cidr.h dict_ht.h dict_ldap.h dict_mysql.h dict_ni.h dict_nis.h \ dict_nisplus.h dict_pcre.h dict_pgsql.h dict_regexp.h \ dict_static.h dict_tcp.h dict_unix.h dir_forest.h events.h \ exec_command.h find_inet.h fsspace.h fullname.h get_domainname.h \ @@ -337,7 +337,8 @@ stream_test: stream_test.c $(LIB) tests: valid_hostname_test mac_expand_test dict_test unescape_test \ hex_quote_test ctable_test inet_addr_list_test base64_code_test \ - attr_scan64_test attr_scan0_test dict_pcre_test host_port_test + attr_scan64_test attr_scan0_test dict_pcre_test host_port_test \ + dict_cidr_test valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref ./valid_hostname valid_hostname.tmp @@ -403,6 +404,11 @@ dict_regexp_test: dict_open dict_regexp.in dict_regexp.map dict_regexp.ref diff dict_regexp.ref dict_regexp.tmp rm -f dict_regexp.tmp +dict_cidr_test: dict_open dict_cidr.in dict_cidr.map dict_cidr.ref + ./dict_open cidr:dict_cidr.map read dict_cidr.tmp 2>&1 + diff dict_cidr.ref dict_cidr.tmp + rm -f dict_cidr.tmp + host_port_test: host_port host_port.in host_port.ref ./host_port host_port.tmp 2>&1 diff host_port.ref host_port.tmp @@ -529,6 +535,19 @@ dict_alloc.o: dict.h dict_alloc.o: vstream.h dict_alloc.o: vbuf.h dict_alloc.o: argv.h +dict_cidr.o: dict_cidr.c +dict_cidr.o: sys_defs.h +dict_cidr.o: mymalloc.h +dict_cidr.o: msg.h +dict_cidr.o: vstream.h +dict_cidr.o: vbuf.h +dict_cidr.o: vstring.h +dict_cidr.o: stringops.h +dict_cidr.o: readlline.h +dict_cidr.o: dict.h +dict_cidr.o: argv.h +dict_cidr.o: dict_cidr.h +dict_cidr.o: split_at.h dict_db.o: dict_db.c dict_db.o: sys_defs.h dict_db.o: msg.h @@ -619,6 +638,7 @@ dict_open.o: dict_pgsql.h dict_open.o: dict_pcre.h dict_open.o: dict_regexp.h dict_open.o: dict_static.h +dict_open.o: dict_cidr.h dict_open.o: stringops.h dict_open.o: vstring.h dict_open.o: split_at.h diff --git a/postfix/src/util/dict_cidr.c b/postfix/src/util/dict_cidr.c new file mode 100644 index 000000000..6c068031e --- /dev/null +++ b/postfix/src/util/dict_cidr.c @@ -0,0 +1,242 @@ +/*++ +/* NAME +/* dict_cidr 3 +/* SUMMARY +/* Dictionary interface for CIDR data +/* SYNOPSIS +/* #include +/* +/* DICT *dict_cidr_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_cidr_open() opens the named file and stores +/* the key/value pairs where the key must be either a +/* "naked" IP address or a netblock in CIDR notation. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Jozsef Kadlecsik +/* kadlec@blackhole.kfki.hu +/* KFKI Research Institute for Particle and Nuclear Physics +/* POB. 49 +/* 1525 Budapest, Hungary +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * Each rule in a CIDR table is parsed and stored in a linked list. + * Obviously all this is IPV4 specific and needs to be redone for IPV6. + */ +typedef struct DICT_CIDR_ENTRY { + unsigned long net_bits; /* network portion of address */ + unsigned long mask_bits; /* network mask */ + char *value; /* lookup result */ + struct DICT_CIDR_ENTRY *next; /* next entry */ +} DICT_CIDR_ENTRY; + +typedef struct { + DICT dict; /* generic members */ + DICT_CIDR_ENTRY *head; /* first entry */ +} DICT_CIDR; + +#define BITS_PER_ADDR 32 + +/* dict_cidr_lookup - CIDR table lookup */ + +static const char *dict_cidr_lookup(DICT *dict, const char *key) +{ + DICT_CIDR *dict_cidr = (DICT_CIDR *) dict; + DICT_CIDR_ENTRY *entry; + unsigned long addr; + + if (msg_verbose) + msg_info("dict_cidr_lookup: %s: %s", dict_cidr->dict.name, key); + + if ((addr = inet_addr(key)) == INADDR_NONE) + return (0); + + for (entry = dict_cidr->head; entry; entry = entry->next) + if ((addr & entry->mask_bits) == entry->net_bits) + return (entry->value); + + return (0); +} + +/* dict_cidr_close - close the CIDR table */ + +static void dict_cidr_close(DICT *dict) +{ + DICT_CIDR *dict_cidr = (DICT_CIDR *) dict; + DICT_CIDR_ENTRY *entry; + DICT_CIDR_ENTRY *next; + + for (entry = dict_cidr->head; entry; entry = next) { + next = entry->next; + myfree(entry->value); + myfree((char *) entry); + } + dict_free(dict); +} + +/* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */ + +static DICT_CIDR_ENTRY *dict_cidr_parse_rule(const char *mapname, int lineno, + char *p) +{ + DICT_CIDR_ENTRY *rule; + char *key; + char *value; + char *mask; + int mask_shift; + unsigned long net_bits; + unsigned long mask_bits; + struct in_addr net_addr; + + /* + * Split into key and value. We already eliminated leading whitespace, + * comments, empty lines or lines with whitespace only. This means a null + * key can't happen but we will handle this anyway. + */ + key = p; + while (*p && !ISSPACE(*p)) /* Skip over key */ + p++; + if (*p) /* Terminate key */ + *p++ = 0; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + value = p; + trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */ + if (*key == 0) { + msg_warn("cidr map %s, line %d: no address pattern: skipping this rule", + mapname, lineno); + return (0); + } + if (*value == 0) { + msg_warn("cidr map %s, line %d: no lookup result: skipping this rule", + mapname, lineno); + return (0); + } + + /* + * Parse the key into network and mask, and destroy the key. Treat a bare + * network address as /32. + */ + if ((mask = split_at(key, '/')) != 0) { + if ((mask_shift = atoi(mask)) <= 0 || mask_shift > BITS_PER_ADDR + || (net_bits = inet_addr(key)) == INADDR_NONE) { + msg_warn("cidr map %s, line %d: bad net/mask pattern: \"%s/%s\": " + "skipping this rule", mapname, lineno, key, mask); + return (0); + } + mask_bits = htonl((0xffffffff) << (BITS_PER_ADDR - mask_shift)); + if (net_bits & ~mask_bits) { + net_addr.s_addr = (net_bits & mask_bits); + msg_warn("cidr map %s, line %d: net/mask pattern \"%s/%s\" with " + "non-null host portion: skipping this rule", + mapname, lineno, key, mask); + msg_warn("specify \"%s/%d\" if this is really what you want", + inet_ntoa(net_addr), mask_shift); + return (0); + } + } else { + if ((net_bits = inet_addr(key)) == INADDR_NONE) { + msg_warn("cidr map %s, line %d: bad address pattern: \"%s\": " + "skipping this rule", mapname, lineno, key); + return (0); + } + mask_shift = 32; + mask_bits = htonl(0xffffffff); + } + + rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY)); + rule->net_bits = net_bits; + rule->mask_bits = mask_bits; + rule->value = mystrdup(value); + rule->next = 0; + + if (msg_verbose) + msg_info("dict_cidr_open: %s: %lu/%d %s", + mapname, rule->net_bits, mask_shift, rule->value); + + return (rule); +} + +/* dict_cidr_open - parse CIDR table */ + +DICT *dict_cidr_open(const char *mapname, int unused_flags, int dict_flags) +{ + DICT_CIDR *dict_cidr; + VSTREAM *map_fp; + VSTRING *line_buffer = vstring_alloc(100); + DICT_CIDR_ENTRY *rule; + DICT_CIDR_ENTRY *last_rule = 0; + int lineno = 0; + + /* + * XXX Eliminate unnecessary queries by setting a flag that says "this + * map matches network addresses only". + */ + dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname, + sizeof(*dict_cidr)); + dict_cidr->dict.lookup = dict_cidr_lookup; + dict_cidr->dict.close = dict_cidr_close; + dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dict_cidr->head = 0; + + if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", mapname); + + while (readlline(line_buffer, map_fp, &lineno)) { + rule = dict_cidr_parse_rule(mapname, lineno, vstring_str(line_buffer)); + if (rule == 0) + continue; + if (last_rule == 0) + dict_cidr->head = rule; + else + last_rule->next = rule; + last_rule = rule; + } + + /* + * Clean up. + */ + if (vstream_fclose(map_fp)) + msg_fatal("cidr map %s: read error: %m", mapname); + vstring_free(line_buffer); + + return (DICT_DEBUG (&dict_cidr->dict)); +} diff --git a/postfix/src/util/dict_cidr.h b/postfix/src/util/dict_cidr.h new file mode 100644 index 000000000..308a7654e --- /dev/null +++ b/postfix/src/util/dict_cidr.h @@ -0,0 +1,43 @@ +#ifndef _DICT_CIDR_H_INCLUDED_ +#define _DICT_CIDR_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cidr 3h +/* SUMMARY +/* Dictionary manager interface to handle cidr data. +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern DICT *dict_cidr_open(const char *, int, int); + +#define DICT_TYPE_CIDR "cidr" + +/* 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 +/* +/* Jozsef Kadlecsik +/* kadlec@blackhole.kfki.hu +/* KFKI Research Institute for Particle and Nuclear Physics +/* POB. 49 +/* 1525 Budapest 114, Hungary +/*--*/ + +#endif diff --git a/postfix/src/util/dict_cidr.in b/postfix/src/util/dict_cidr.in new file mode 100644 index 000000000..bfcb0806e --- /dev/null +++ b/postfix/src/util/dict_cidr.in @@ -0,0 +1,7 @@ +get 172.16.0.0 +get 172.16.0.1 +get 172.16.7.255 +get 172.16.8.1 +get 172.16.17.1 +get 172.17.1.1 +get 172.17.1.2 diff --git a/postfix/src/util/dict_cidr.map b/postfix/src/util/dict_cidr.map new file mode 100644 index 000000000..a655ac4cf --- /dev/null +++ b/postfix/src/util/dict_cidr.map @@ -0,0 +1,9 @@ +172.16.0.0/21 554 match bad netblock 172.16.0.0/21 +172.16.8.0/21 554 match bad netblock 172.16.8.0/21 +172.16.0.0/16 554 match bad netblock 172.16.0.0/16 +172.17.1.1 554 match bad naked address +172.16.1.3/21 whatever +172.16.1.3/33 whatever +172.999.0.0/21 whatever +172.16.1.999 whatever +172.16.1.4 diff --git a/postfix/src/util/dict_cidr.ref b/postfix/src/util/dict_cidr.ref new file mode 100644 index 000000000..a1fe357b8 --- /dev/null +++ b/postfix/src/util/dict_cidr.ref @@ -0,0 +1,13 @@ +./dict_open: warning: cidr map dict_cidr.map, line 5: net/mask pattern "172.16.1.3/21" with non-null host portion: skipping this rule +./dict_open: warning: specify "172.16.0.0/21" if this is really what you want +./dict_open: warning: cidr map dict_cidr.map, line 6: bad net/mask pattern: "172.16.1.3/33": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 7: bad net/mask pattern: "172.999.0.0/21": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 8: bad address pattern: "172.16.1.999": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 9: no lookup result: skipping this rule +172.16.0.0=554 match bad netblock 172.16.0.0/21 +172.16.0.1=554 match bad netblock 172.16.0.0/21 +172.16.7.255=554 match bad netblock 172.16.0.0/21 +172.16.8.1=554 match bad netblock 172.16.8.0/21 +172.16.17.1=554 match bad netblock 172.16.0.0/16 +172.17.1.1=554 match bad naked address +172.17.1.2: not found diff --git a/postfix/src/util/dict_ldap.c b/postfix/src/util/dict_ldap.c index 8b21278d4..c4867ba65 100644 --- a/postfix/src/util/dict_ldap.c +++ b/postfix/src/util/dict_ldap.c @@ -57,12 +57,27 @@ /* If you must bind to the server, do it with this distinguished name ... /* .IP \fIldapsource_\fRbind_pw /* \&... and this password. -/* .IP \fIldapsource_\fRcache +/* .IP \fIldapsource_\fRcache (no longer supported) /* Whether or not to turn on client-side caching. -/* .IP \fIldapsource_\fRcache_expiry +/* .IP \fIldapsource_\fRcache_expiry (no longer supported) /* If you do cache results, expire them after this many seconds. -/* .IP \fIldapsource_\fRcache_size +/* .IP \fIldapsource_\fRcache_size (no longer supported) /* The cache size in bytes. Does nothing if the cache is off, of course. +/* .IP \fIldapsource_\fRrecursion_limit +/* Maximum recursion depth when expanding DN or URL references. +/* Queries which exceed the recursion limit fail with +/* dict_errno = DICT_ERR_RETRY. +/* .IP \fIldapsource_\fRexpansion_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 value of a multivalued result attribute counts as one result. +/* .IP \fIldapsource_\fRsize_limit +/* Limit on the number of entries returned by individual LDAP queries. +/* Queries which exceed the limit fail with dict_errno=DICT_ERR_RETRY. +/* This is an *entry* count, for any single query performed during the +/* possibly recursive lookup. +/* .IP \fIldapsource_\fRchase_referrals +/* Controls whether LDAP referrals are obeyed. /* .IP \fIldapsource_\fRdereference /* How to handle LDAP aliases. See ldap.h or ldap_open(3) man page. /* .IP \fIldapsource_\fRdebuglevel @@ -152,10 +167,10 @@ typedef struct { char *bind_dn; char *bind_pw; int timeout; - int cache; - long cache_expiry; - long cache_size; int dereference; + long recursion_limit; + long expansion_limit; + long size_limit; int chase_referrals; int debuglevel; int version; @@ -231,11 +246,6 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) char *myname = "dict_ldap_connect"; int rc = 0; -#ifdef LDAP_API_FEATURE_X_MEMCACHE - LDAPMemCache *dircache; - -#endif - #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval mytimeval; @@ -313,6 +323,16 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) } #endif + /* + * Limit the number of entries returned by each query. + */ + if (dict_ldap->size_limit) { + 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); + } + /* * Configure alias dereferencing for this connection. Thanks to Mike * Mattice for this, and to Hery Rakotoarisoa for the v3 update. @@ -370,52 +390,6 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) msg_info("%s: Successful bind to server %s as %s ", myname, dict_ldap->server_host, dict_ldap->bind_dn); } - - /* - * Set up client-side caching if it's configured. - */ - if (dict_ldap->cache) { - if (msg_verbose) - msg_info - ("%s: Enabling %ld-byte cache for %s with %ld-second expiry", - myname, dict_ldap->cache_size, dict_ldap->ldapsource, - dict_ldap->cache_expiry); - -#ifdef LDAP_API_FEATURE_X_MEMCACHE - rc = ldap_memcache_init(dict_ldap->cache_expiry, dict_ldap->cache_size, - NULL, NULL, &dircache); - if (rc != LDAP_SUCCESS) { - msg_warn - ("%s: Unable to configure cache for %s: %d (%s) -- continuing", - myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); - } else { - rc = ldap_memcache_set(dict_ldap->ld, dircache); - if (rc != LDAP_SUCCESS) { - msg_warn - ("%s: Unable to configure cache for %s: %d (%s) -- continuing", - myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); - } else { - if (msg_verbose) - msg_info("%s: Caching enabled for %s", - myname, dict_ldap->ldapsource); - } - } -#else - - rc = ldap_enable_cache(dict_ldap->ld, dict_ldap->cache_expiry, - dict_ldap->cache_size); - if (rc != LDAP_SUCCESS) { - msg_warn - ("%s: Unable to configure cache for %s: %d (%s) -- continuing", - myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); - } else { - if (msg_verbose) - msg_info("%s: Caching enabled for %s", - myname, dict_ldap->ldapsource); - } - -#endif - } if (msg_verbose) msg_info("%s: Cached connection handle for LDAP source %s", myname, dict_ldap->ldapsource); @@ -426,7 +400,8 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) /* * expand a filter (lookup or result) */ -static void dict_ldap_expand_filter(char *filter, char *value, VSTRING *out) +static void dict_ldap_expand_filter(char *ldapsource, char *filter, + char *value, VSTRING *out) { char *myname = "dict_ldap_expand_filter"; char *sub, @@ -461,9 +436,8 @@ static void dict_ldap_expand_filter(char *filter, char *value, VSTRING *out) vstring_strcat(out, u); break; default: - msg_warn - ("%s: Invalid filter substitution format '%%%c'!", - myname, *(sub + 1)); + msg_warn("%s: %s: Invalid filter substitution format '%%%c'!", + myname, ldapsource, *(sub + 1)); /* fall through */ case 's': vstring_strcat(out, u); @@ -486,6 +460,9 @@ static void dict_ldap_expand_filter(char *filter, char *value, VSTRING *out) static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, VSTRING *result) { + static int recursion = 0; + static int expansion; + long entries = 0; long i = 0; int rc = 0; LDAPMessage *resloop = 0; @@ -500,13 +477,27 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, tv.tv_sec = dict_ldap->timeout; tv.tv_usec = 0; + if (++recursion == 1) + expansion = 0; + if (msg_verbose) - msg_info("%s: Search found %d match(es)", myname, + msg_info("%s[%d]: Search found %d match(es)", myname, recursion, ldap_count_entries(dict_ldap->ld, res)); for (entry = ldap_first_entry(dict_ldap->ld, res); entry != NULL; entry = ldap_next_entry(dict_ldap->ld, entry)) { ber = NULL; + + /* + * 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); + dict_errno = DICT_ERR_RETRY; + } for (attr = ldap_first_attribute(dict_ldap->ld, entry, &ber); attr != NULL; ldap_memfree(attr), attr = ldap_next_attribute(dict_ldap->ld, @@ -514,17 +505,38 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, vals = ldap_get_values(dict_ldap->ld, entry, attr); if (vals == NULL) { if (msg_verbose) - msg_info("%s: Entry doesn't have any values for %s", - myname, attr); + msg_info("%s[%d]: Entry doesn't have any values for %s", + myname, recursion, attr); + continue; + } + + /* + * If we previously encountered an error, we still continue + * through the loop, to avoid memory leaks, but we don't waste + * time accumulating any further results. + * + * XXX: There may be a more efficient way to exit the loop with no + * leaks, but it will likely be more fragile and not worth the + * extra code. + */ + if (dict_errno != 0 || vals[0] == 0) { + ldap_value_free(vals); continue; } + + /* + * The "result_attributes" list enumerates all the requested + * attributes, first the ordinary result attribtutes and then the + * special result attributes that hold DN or LDAP URL values. + * + * The number of ordinary attributes is "num_attributes". + * + * We compute the attribute type (ordinary or special) from its + * index on the "result_attributes" list. + */ for (i = 0; dict_ldap->result_attributes->argv[i]; i++) { - if (strcasecmp(dict_ldap->result_attributes->argv[i], - attr) == 0) { - if (msg_verbose) - msg_info("%s: search returned %ld value(s) for requested result attribute %s", myname, i, attr); + if (strcasecmp(dict_ldap->result_attributes->argv[i], attr) == 0) break; - } } /* @@ -532,21 +544,39 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, * recursing (for dn or url attributes). */ 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]); + 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->result_filter, + dict_ldap_expand_filter(dict_ldap->ldapsource, + dict_ldap->result_filter, vals[i], result); } - } else if (dict_ldap->result_attributes->argv[i]) { + if (dict_errno != 0) + continue; + if (msg_verbose) + msg_info("%s[%d]: search returned %ld value(s) for" + " requested result attribute %s", + myname, recursion, i, attr); + } else if (recursion < dict_ldap->recursion_limit + && dict_ldap->result_attributes->argv[i]) { + /* Special result attribute */ for (i = 0; vals[i] != NULL; i++) { if (ldap_is_ldap_url(vals[i])) { if (msg_verbose) - msg_info("%s: looking up URL %s", myname, - vals[i]); + msg_info("%s[%d]: looking up URL %s", myname, + recursion, vals[i]); rc = ldap_url_parse(vals[i], &url); if (rc == 0) { rc = ldap_search_st(dict_ldap->ld, url->lud_dn, @@ -557,7 +587,8 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, } } else { if (msg_verbose) - msg_info("%s: looking up DN %s", myname, vals[i]); + msg_info("%s[%d]: looking up DN %s", + myname, recursion, vals[i]); rc = ldap_search_st(dict_ldap->ld, vals[i], LDAP_SCOPE_BASE, "objectclass=*", dict_ldap->result_attributes->argv, @@ -573,27 +604,44 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, * Go ahead and treat this as though the DN existed * and just didn't have any result attributes. */ - msg_warn("%s: DN %s not found, skipping ", myname, - vals[i]); + msg_warn("%s[%d]: DN %s not found, skipping ", myname, + recursion, vals[i]); break; default: - msg_warn("%s: search error %d: %s ", myname, rc, - ldap_err2string(rc)); + msg_warn("%s[%d]: search error %d: %s ", myname, + recursion, rc, ldap_err2string(rc)); dict_errno = DICT_ERR_RETRY; break; } if (resloop != 0) ldap_msgfree(resloop); + + if (dict_errno != 0) + break; } + if (dict_errno != 0) + continue; + if (msg_verbose) + msg_info("%s[%d]: search returned %ld value(s) for" + " special result attribute %s", + myname, recursion, i, attr); + } 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]); + dict_errno = DICT_ERR_RETRY; } ldap_value_free(vals); } if (ber) ber_free(ber, 0); } + if (msg_verbose) - msg_info("%s: Leaving %s", myname, myname); + msg_info("%s[%d]: Leaving %s", myname, recursion, myname); + --recursion; } /* dict_ldap_lookup - find database entry */ @@ -720,11 +768,11 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) /* * No, log the fact and continue. */ - msg_warn("%s: Fixed query_filter %s is probably useless", myname, - dict_ldap->query_filter); + 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->query_filter, + dict_ldap_expand_filter(dict_ldap->ldapsource, dict_ldap->query_filter, vstring_str(escaped_name), filter_buf); } @@ -858,6 +906,7 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) char *domainlist; char *scope; char *attr; + int tmp; if (msg_verbose) msg_info("%s: Using LDAP source %s", myname, ldapsource); @@ -1040,31 +1089,58 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) * get configured value of "ldapsource_cache"; default to false */ vstring_sprintf(config_param, "%s_cache", ldapsource); - dict_ldap->cache = get_mail_conf_bool(vstring_str(config_param), 0); - if (msg_verbose) - msg_info("%s: %s is %d", myname, vstring_str(config_param), - dict_ldap->cache); + tmp = get_mail_conf_bool(vstring_str(config_param), 0); + if (tmp) + msg_warn("%s: ignoring %s", myname, vstring_str(config_param)); /* * get configured value of "ldapsource_cache_expiry"; default to 30 * seconds */ vstring_sprintf(config_param, "%s_cache_expiry", ldapsource); - dict_ldap->cache_expiry = get_mail_conf_int(vstring_str(config_param), - 30, 0, 0); - if (msg_verbose) - msg_info("%s: %s is %ld", myname, vstring_str(config_param), - dict_ldap->cache_expiry); + tmp = get_mail_conf_int(vstring_str(config_param), -1, 0, 0); + if (tmp >= 0) + msg_warn("%s: ignoring %s", myname, vstring_str(config_param)); /* * get configured value of "ldapsource_cache_size"; default to 32k */ vstring_sprintf(config_param, "%s_cache_size", ldapsource); - dict_ldap->cache_size = get_mail_conf_int(vstring_str(config_param), - 32768, 0, 0); + tmp = get_mail_conf_int(vstring_str(config_param), -1, 0, 0); + if (tmp >= 0) + msg_warn("%s: ignoring %s", myname, vstring_str(config_param)); + + /* + * get configured value of "ldapsource_recursion_limit"; default to 1000 + */ + vstring_sprintf(config_param, "%s_recursion_limit", ldapsource); + dict_ldap->recursion_limit = get_mail_conf_int(vstring_str(config_param), + 1000, 1, 0); + if (msg_verbose) + msg_info("%s: %s is %ld", myname, vstring_str(config_param), + dict_ldap->recursion_limit); + + /* + * get configured value of "ldapsource_expansion_limit"; default to 1000 + */ + vstring_sprintf(config_param, "%s_expansion_limit", ldapsource); + dict_ldap->expansion_limit = get_mail_conf_int(vstring_str(config_param), + 0, 0, 0); + if (msg_verbose) + msg_info("%s: %s is %ld", myname, vstring_str(config_param), + dict_ldap->expansion_limit); + + /* + * get configured value of "ldapsource_size_limit"; default to + * expansion_limit + */ + vstring_sprintf(config_param, "%s_size_limit", ldapsource); + dict_ldap->size_limit = get_mail_conf_int(vstring_str(config_param), + dict_ldap->expansion_limit, + 0, 0); if (msg_verbose) msg_info("%s: %s is %ld", myname, vstring_str(config_param), - dict_ldap->cache_size); + dict_ldap->size_limit); /* * Alias dereferencing suggested by Mike Mattice. diff --git a/postfix/src/util/dict_open.c b/postfix/src/util/dict_open.c index 0e01366f2..32fa0ae49 100644 --- a/postfix/src/util/dict_open.c +++ b/postfix/src/util/dict_open.c @@ -178,6 +178,7 @@ #include #include #include +#include #include #include #include @@ -193,9 +194,7 @@ typedef struct { static DICT_OPEN_INFO dict_open_info[] = { DICT_TYPE_ENVIRON, dict_env_open, DICT_TYPE_UNIX, dict_unix_open, -#if 0 DICT_TYPE_TCP, dict_tcp_open, -#endif #ifdef HAS_DBM DICT_TYPE_DBM, dict_dbm_open, #endif @@ -228,6 +227,7 @@ static DICT_OPEN_INFO dict_open_info[] = { DICT_TYPE_REGEXP, dict_regexp_open, #endif DICT_TYPE_STATIC, dict_static_open, + DICT_TYPE_CIDR, dict_cidr_open, 0, }; diff --git a/postfix/src/util/dict_pcre.c b/postfix/src/util/dict_pcre.c index 38c77b902..4745281e0 100644 --- a/postfix/src/util/dict_pcre.c +++ b/postfix/src/util/dict_pcre.c @@ -73,6 +73,7 @@ typedef struct { char *regexp; /* regular expression */ int options; /* options */ + int match; /* positive or negative match */ } DICT_PCRE_REGEXP; typedef struct { @@ -95,12 +96,14 @@ typedef struct { pcre *pattern; /* compiled pattern */ pcre_extra *hints; /* hints to speed pattern execution */ char *replacement; /* replacement string */ + int match; /* positive or negative match */ } DICT_PCRE_MATCH_RULE; typedef struct { DICT_PCRE_RULE rule; /* generic members */ pcre *pattern; /* compiled pattern */ pcre_extra *hints; /* hints to speed pattern execution */ + int match; /* positive or negative match */ } DICT_PCRE_IF_RULE; /* @@ -132,6 +135,7 @@ typedef struct { const char *mapname; /* name of regexp map */ int lineno; /* where in file */ int flags; /* dict_flags */ + size_t max_sub; /* Largest $n seen */ } DICT_PCRE_PRESCAN_CONTEXT; /* @@ -251,13 +255,23 @@ static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string) lookup_string, lookup_len, NULL_STARTOFFSET, NULL_EXEC_OPTIONS, ctxt.offsets, PCRE_MAX_CAPTURE * 3); - if (ctxt.matches == PCRE_ERROR_NOMATCH) - continue; - if (ctxt.matches <= 0) { + + if (ctxt.matches > 0) { + if (!match_rule->match) + continue; /* Negative rule matched */ + } else if (ctxt.matches == PCRE_ERROR_NOMATCH) { + if (match_rule->match) + continue; /* Positive rule did not + * match */ + } else { dict_pcre_exec_error(dict->name, rule->lineno, ctxt.matches); - continue; + continue; /* pcre_exec failed */ } + /* Negative rules can't have any substitutions */ + if (!match_rule->match) + return match_rule->replacement; + /* * We've got a match. Perform substitution on replacement string. */ @@ -289,11 +303,17 @@ static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string) lookup_string, lookup_len, NULL_STARTOFFSET, NULL_EXEC_OPTIONS, ctxt.offsets, PCRE_MAX_CAPTURE * 3); - if (ctxt.matches == PCRE_ERROR_NOMATCH) - continue; - if (ctxt.matches <= 0) { + + if (ctxt.matches > 0) { + if (!if_rule->match) + continue; /* Negative rule matched */ + } else if (ctxt.matches == PCRE_ERROR_NOMATCH) { + if (if_rule->match) + continue; /* Positive rule did not + * match */ + } else { dict_pcre_exec_error(dict->name, rule->lineno, ctxt.matches); - continue; + continue; /* pcre_exec failed */ } nesting++; continue; @@ -359,6 +379,25 @@ static int dict_pcre_get_pattern(const char *mapname, int lineno, char **bufp, char *p = *bufp; char re_delimiter; + /* + * Process negation operators. + */ + pattern->match = 1; + while (*p == '!') { + pattern->match = !pattern->match; + p++; + } + + /* + * Grr...aceful handling of whitespace after '!'. + */ + while (*p && ISSPACE(*p)) + p++; + if (*p == 0) { + msg_warn("pcre map %s, line %d: no regexp: skipping this rule", + mapname, lineno); + return (0); + } re_delimiter = *p++; pattern->regexp = p; @@ -433,11 +472,10 @@ static int dict_pcre_prescan(int type, VSTRING *buf, char *context) if (type == MAC_PARSE_VARNAME) { if (ctxt->flags & DICT_FLAG_NO_REGSUB) { msg_warn("pcre map %s, line %d: " - "regular expression substitution is not allowed", - ctxt->mapname, ctxt->lineno); + "regular expression substitution is not allowed", + ctxt->mapname, ctxt->lineno); return (MAC_PARSE_ERROR); } - if (!alldig(vstring_str(buf))) { msg_warn("pcre map %s, line %d: non-numeric replacement index \"%s\"", ctxt->mapname, ctxt->lineno, vstring_str(buf)); @@ -449,6 +487,8 @@ static int dict_pcre_prescan(int type, VSTRING *buf, char *context) ctxt->mapname, ctxt->lineno, vstring_str(buf)); return (MAC_PARSE_ERROR); } + if (n > ctxt->max_sub) + ctxt->max_sub = n; } return (MAC_PARSE_OK); } @@ -536,6 +576,7 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, prescan_context.mapname = mapname; prescan_context.lineno = lineno; prescan_context.flags = dict_flags; + prescan_context.max_sub = 0; if (mac_parse(p, dict_pcre_prescan, (char *) &prescan_context) & MAC_PARSE_ERROR) { @@ -544,6 +585,15 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, return (0); } + /* + * Substring replacement not possible with negative regexps. + */ + if (prescan_context.max_sub > 0 && regexp.match == 0) { + msg_warn("pcre map %s, line %d: $number found in negative match " + "replacement text: skipping this rule", mapname, lineno); + return (0); + } + /* * Compile the pattern. */ @@ -556,6 +606,7 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, match_rule = (DICT_PCRE_MATCH_RULE *) dict_pcre_rule_alloc(DICT_PCRE_OP_MATCH, nesting, lineno, sizeof(DICT_PCRE_MATCH_RULE)); + match_rule->match = regexp.match; match_rule->replacement = mystrdup(p); match_rule->pattern = engine.pattern; match_rule->hints = engine.hints; @@ -583,6 +634,8 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, /* * Warn about out-of-place text. */ + while (*p && ISSPACE(*p)) + ++p; if (*p) msg_warn("pcre map %s, line %d: ignoring extra text after IF", mapname, lineno); @@ -599,6 +652,7 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, if_rule = (DICT_PCRE_IF_RULE *) dict_pcre_rule_alloc(DICT_PCRE_OP_IF, nesting, lineno, sizeof(DICT_PCRE_IF_RULE)); + if_rule->match = regexp.match; if_rule->pattern = engine.pattern; if_rule->hints = engine.hints; return ((DICT_PCRE_RULE *) if_rule); @@ -624,6 +678,8 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, /* * Warn about out-of-place text. */ + while (*p && ISSPACE(*p)) + ++p; if (*p) msg_warn("pcre map %s, line %d: ignoring extra text after ENDIF", mapname, lineno); diff --git a/postfix/src/util/dict_pcre.in b/postfix/src/util/dict_pcre.in index 1dcba54a1..324342e83 100644 --- a/postfix/src/util/dict_pcre.in +++ b/postfix/src/util/dict_pcre.in @@ -8,3 +8,7 @@ get c get d get 1234 get 123 +get bar/find +get bar/whynot +get bar/elbereth +get say/elbereth diff --git a/postfix/src/util/dict_pcre.map b/postfix/src/util/dict_pcre.map index 690899c3f..948b5c353 100644 --- a/postfix/src/util/dict_pcre.map +++ b/postfix/src/util/dict_pcre.map @@ -10,3 +10,12 @@ endif /(1)(2)(3)(5)/ ($1)($2)($3)($4)($5) /(1)(2)(3)(4)/ ($1)($2)($3)($4) /(1)(2)(3)/ ($1)($2)($3) +# trailing whitespace below +if /bar/ +if !/xyzzy/ +/(elbereth)/ ($1) +!/(bogus)/ ($1) +!/find/ Don't have a liquor license +endif +endif +# trailing whitespace above diff --git a/postfix/src/util/dict_pcre.ref b/postfix/src/util/dict_pcre.ref index daae8bcec..4b5910790 100644 --- a/postfix/src/util/dict_pcre.ref +++ b/postfix/src/util/dict_pcre.ref @@ -2,6 +2,7 @@ ./dict_open: warning: pcre map dict_pcre.map, line 5: ignoring extra text after ENDIF ./dict_open: warning: pcre map dict_pcre.map, line 8: unknown regexp option "!": skipping this rule ./dict_open: warning: dict_pcre.map, line 9: no replacement text: using empty string +./dict_open: warning: pcre map dict_pcre.map, line 17: $number found in negative match replacement text: skipping this rule true: not found true1=1 true2: not found @@ -12,3 +13,7 @@ c= d: not found 1234=(1)(2)(3)(4) 123=(1)(2)(3) +bar/find: not found +bar/whynot=Don't have a liquor license +bar/elbereth=(elbereth) +say/elbereth: not found diff --git a/postfix/src/util/dict_regexp.c b/postfix/src/util/dict_regexp.c index 5735ca8a8..73ea34f28 100644 --- a/postfix/src/util/dict_regexp.c +++ b/postfix/src/util/dict_regexp.c @@ -584,9 +584,9 @@ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *mapname, int lineno, first_pat.options |= REG_NOSUB; } else if (dict_flags & DICT_FLAG_NO_REGSUB) { msg_warn("regexp map %s, line %d: " - "regular expression substitution is not allowed: " - "skipping this rule", mapname, lineno); - return(0); + "regular expression substitution is not allowed: " + "skipping this rule", mapname, lineno); + return (0); } if ((first_exp = dict_regexp_compile_pat(mapname, lineno, &first_pat)) == 0) @@ -636,6 +636,8 @@ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *mapname, int lineno, p++; if (!dict_regexp_get_pat(mapname, lineno, &p, &pattern)) return (0); + while (*p && ISSPACE(*p)) + ++p; if (*p) msg_warn("regexp map %s, line %d: ignoring extra text after IF", mapname, lineno); @@ -661,6 +663,8 @@ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *mapname, int lineno, mapname, lineno); return (0); } + while (*p && ISSPACE(*p)) + ++p; if (*p) msg_warn("regexp map %s, line %d: ignoring extra text after ENDIF", mapname, lineno); diff --git a/postfix/src/util/dict_regexp.in b/postfix/src/util/dict_regexp.in index 4e4b473b1..d5f3b2ed0 100644 --- a/postfix/src/util/dict_regexp.in +++ b/postfix/src/util/dict_regexp.in @@ -11,3 +11,7 @@ get aa get 1235 get 1234 get 123 +get bar/find +get bar/whynot +get bar/elbereth +get say/elbereth diff --git a/postfix/src/util/dict_regexp.map b/postfix/src/util/dict_regexp.map index 690899c3f..948b5c353 100644 --- a/postfix/src/util/dict_regexp.map +++ b/postfix/src/util/dict_regexp.map @@ -10,3 +10,12 @@ endif /(1)(2)(3)(5)/ ($1)($2)($3)($4)($5) /(1)(2)(3)(4)/ ($1)($2)($3)($4) /(1)(2)(3)/ ($1)($2)($3) +# trailing whitespace below +if /bar/ +if !/xyzzy/ +/(elbereth)/ ($1) +!/(bogus)/ ($1) +!/find/ Don't have a liquor license +endif +endif +# trailing whitespace above diff --git a/postfix/src/util/dict_regexp.ref b/postfix/src/util/dict_regexp.ref index 2ca40eb93..8cf7d8234 100644 --- a/postfix/src/util/dict_regexp.ref +++ b/postfix/src/util/dict_regexp.ref @@ -2,6 +2,7 @@ ./dict_open: warning: regexp map dict_regexp.map, line 5: ignoring extra text after ENDIF ./dict_open: warning: regexp map dict_regexp.map, line 9: using empty replacement string ./dict_open: warning: regexp map dict_regexp.map, line 10: out of range replacement index "5": skipping this rule +./dict_open: warning: regexp map dict_regexp.map, line 17: $number found in negative match replacement text: skipping this rule true: not found true1=1 true2: not found @@ -15,3 +16,7 @@ aa=a!b 1235=(1)(2)(3) 1234=(1)(2)(3)(4) 123=(1)(2)(3) +bar/find: not found +bar/whynot=Don't have a liquor license +bar/elbereth=(elbereth) +say/elbereth: not found diff --git a/postfix/src/util/dict_tcp.c b/postfix/src/util/dict_tcp.c index 668605580..b7345feb8 100644 --- a/postfix/src/util/dict_tcp.c +++ b/postfix/src/util/dict_tcp.c @@ -27,8 +27,8 @@ /* ENCODING /* .ad /* .fi -/* In request and reply parameters, the character % and any non-printable -/* characters (including whitespace) are replaced by %XX, XX being the +/* In request and reply parameters, the character % and any non-printing +/* and whitespace characters must be replaced by %XX, XX being the /* corresponding ASCII hexadecimal character value. The hexadecimal codes /* can be specified in any case (upper, lower, mixed). /* REQUEST FORMAT @@ -43,7 +43,8 @@ /* REPLY FORMAT /* .ad /* .fi -/* Replies can have the following form: +/* Replies must be no longer than 4096 characters including the +/* newline terminator, and must have the following form: /* .IP "500 SPACE optional-text NEWLINE" /* In case of a lookup request, the requested data does not exist. /* In case of an update request, the request was rejected. @@ -83,16 +84,16 @@ /* Utility library. */ -#include "msg.h" -#include "mymalloc.h" -#include "vstring.h" -#include "vstream.h" -#include "vstring_vstream.h" -#include "connect.h" -#include "hex_quote.h" -#include "dict.h" -#include "stringops.h" -#include "dict_tcp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* Application-specific. */ @@ -103,8 +104,9 @@ typedef struct { VSTREAM *fp; /* I/O stream */ } DICT_TCP; -#define DICT_TCP_MAXTRY 10 -#define DICT_TCP_TMOUT 100 +#define DICT_TCP_MAXTRY 10 /* attempts before giving up */ +#define DICT_TCP_TMOUT 100 /* connect/read/write timeout */ +#define DICT_TCP_MAXLEN 4096 /* server reply size limit */ #define STR(x) vstring_str(x) @@ -115,10 +117,10 @@ static int dict_tcp_connect(DICT_TCP *dict_tcp) int fd; /* - * Connect to the server. Enforce a time limit on read/write operations - * so that we do not get stuck. + * Connect to the server. Enforce a time limit on all operations so that + * we do not get stuck. */ - if ((fd = inet_connect(dict_tcp->dict.name, BLOCKING, 0)) < 0) { + if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) { msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name); return (-1); } @@ -153,6 +155,7 @@ static const char *dict_tcp_lookup(DICT *dict, const char *key) char *myname = "dict_tcp_lookup"; int tries; char *start; + int last_ch; #define RETURN(errval, result) { dict_errno = errval; return (result); } @@ -173,14 +176,22 @@ static const char *dict_tcp_lookup(DICT *dict, const char *key) */ hex_quote(dict_tcp->hex_buf, key); vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf)); - if (vstring_get_nonl(dict_tcp->hex_buf, dict_tcp->fp) > 0) + if (msg_verbose) + msg_info("%s: send \"get %s\"", myname, STR(dict_tcp->hex_buf)); + last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp, + DICT_TCP_MAXLEN); + if (last_ch == '\n') break; /* * Disconnect from the server if it can't talk to us. */ - msg_warn("read TCP map reply from %s: unexpected EOF (%m)", - dict_tcp->dict.name); + if (last_ch < 0) + msg_warn("read TCP map reply from %s: unexpected EOF (%m)", + dict_tcp->dict.name); + else + msg_warn("read TCP map reply from %s: text longer than %d", + dict_tcp->dict.name, DICT_TCP_MAXLEN); dict_tcp_disconnect(dict_tcp); } @@ -195,6 +206,8 @@ static const char *dict_tcp_lookup(DICT *dict, const char *key) */ sleep(1); } + if (msg_verbose) + msg_info("%s: recv: \"%s\"", myname, STR(dict_tcp->hex_buf)); /* * Check the general reply syntax. If the reply is malformed, disconnect @@ -205,7 +218,7 @@ static const char *dict_tcp_lookup(DICT *dict, const char *key) || !ISDIGIT(start[2]) || !ISSPACE(start[3]) || !hex_unquote(dict_tcp->raw_buf, start + 4)) { msg_warn("read TCP map reply from %s: malformed reply %.100s", - dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); dict_tcp_disconnect(dict_tcp); RETURN(DICT_ERR_RETRY, 0); } @@ -217,7 +230,7 @@ static const char *dict_tcp_lookup(DICT *dict, const char *key) switch (start[0]) { default: msg_warn("read TCP map reply from %s: bad status code %.100s", - dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); dict_tcp_disconnect(dict_tcp); RETURN(DICT_ERR_RETRY, 0); case '4': @@ -268,5 +281,5 @@ DICT *dict_tcp_open(const char *map, int unused_flags, int dict_flags) dict_tcp->dict.lookup = dict_tcp_lookup; dict_tcp->dict.close = dict_tcp_close; dict_tcp->dict.flags = dict_flags | DICT_FLAG_FIXED; - return (DICT_DEBUG(&dict_tcp->dict)); + return (DICT_DEBUG (&dict_tcp->dict)); } diff --git a/postfix/src/util/hex_quote.c b/postfix/src/util/hex_quote.c index f82a92001..662fdddc8 100644 --- a/postfix/src/util/hex_quote.c +++ b/postfix/src/util/hex_quote.c @@ -15,7 +15,8 @@ /* const char *hex; /* DESCRIPTION /* hex_quote() takes a null-terminated string and replaces non-printable -/* characters and % by %XX, XX being the two-digit hexadecimal equivalent. +/* and whitespace characters and the % by %XX, XX being the two-digit +/* hexadecimal equivalent. /* The hexadecimal codes are produced as upper-case characters. The result /* value is the hex argument. /* @@ -61,7 +62,7 @@ VSTRING *hex_quote(VSTRING *hex, const char *raw) VSTRING_RESET(hex); for (cp = raw; (ch = *(unsigned const char *) cp) != 0; cp++) { - if (ch != '%' && ISPRINT(ch)) { + if (ch != '%' && !ISSPACE(ch) && ISPRINT(ch)) { VSTRING_ADDCH(hex, ch); } else { vstring_sprintf_append(hex, "%%%02X", ch); diff --git a/postfix/src/util/match_ops.c b/postfix/src/util/match_ops.c index d1586f7be..5d9e6a5d2 100644 --- a/postfix/src/util/match_ops.c +++ b/postfix/src/util/match_ops.c @@ -69,7 +69,7 @@ #endif #ifndef INADDR_NONE -#define INADDR_NONE 0xffffff +#define INADDR_NONE 0xffffffff #endif /* Utility library. */ diff --git a/postfix/src/util/safe_open.c b/postfix/src/util/safe_open.c index 657f9a32a..c047a5bac 100644 --- a/postfix/src/util/safe_open.c +++ b/postfix/src/util/safe_open.c @@ -244,12 +244,16 @@ VSTREAM *safe_open(const char *path, int flags, int mode, /* * Open an existing file or create a new one, carefully. When opening * an existing file, we are prepared to deal with "no file" errors - * only. Any other error means we better give up trying. + * only. When creating a file, we are prepared for "file exists" + * errors only. Any other error means we better give up trying. */ case O_CREAT: - if ((fp = safe_open_exist(path, flags, st, why)) == 0) - if (errno == ENOENT) - fp = safe_open_create(path, flags, mode, st, user, group, why); + fp = safe_open_exist(path, flags, st, why); + if (fp == 0 && errno == ENOENT) { + fp = safe_open_create(path, flags, mode, st, user, group, why); + if (fp == 0 && errno == EEXIST) + fp = safe_open_exist(path, flags, st, why); + } return (fp); /* diff --git a/postfix/src/util/valid_hostname.c b/postfix/src/util/valid_hostname.c index b0a5f2e35..23dd4b4d7 100644 --- a/postfix/src/util/valid_hostname.c +++ b/postfix/src/util/valid_hostname.c @@ -234,7 +234,7 @@ int valid_hostliteral(const char *addr, int gripe) msg_warn("%s: unexpected text after ']': %.100s", myname, addr); return (0); } - if (last - addr >= sizeof(buf)) { + if (last >= addr + sizeof(buf)) { if (gripe) msg_warn("%s: too much text: %.100s", myname, addr); return (0); diff --git a/postfix/src/verify/verify.c b/postfix/src/verify/verify.c index 443b07ecc..a68b726d3 100644 --- a/postfix/src/verify/verify.c +++ b/postfix/src/verify/verify.c @@ -29,7 +29,8 @@ /* .IP "\fBVRFY_ADDR_UPDATE\fI address status text\fR" /* Update the status of the specified address. /* .IP "\fBVRFY_ADDR_QUERY\fI address\fR" -/* Look up the \fIstatus\fR and \fItext\fR of the specified address. +/* Look up the \fIstatus\fR, \fIlast update time\fR and \fItext\fR +/* of the specified address. /* If the status is unknown, a probe is sent and a default status is /* returned. /* .PP