diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 132e9b83f..de1515131 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -1,3 +1,7 @@ +-THOST +-TPLPGSQL +-TPGSQL_NAME +-TDICT_PGSQL -TABOUNCE -TALIAS_TOKEN -TARGV diff --git a/postfix/CA-2003-12 b/postfix/CA-2003-12 new file mode 100644 index 000000000..e406cd0b3 --- /dev/null +++ b/postfix/CA-2003-12 @@ -0,0 +1,42 @@ +Postfix CA-2003-12 Preliminary REJECT pattern +============================================= + +CERT advisory CA-2003-12 concerns a Sendmail buffer overflow exploit +that can happen with message headers containing the 0xff byte value. + +At this time, 8-bit text in message headers violates Internet email +standards. A properly implemented mail client encodes 8-bit message +header text as 7-bit text. + +According to documentation from Sendmail, some exploits can be +stopped by configuring a gateway MTA to remove 0xff bytes from +message headers. This provides partial protection, because downstream +Sendmail systems may still use untrusted information from the DNS +while (re)writing message headers. + +For the same reason, configuring a gateway MTA to limit the length +of message headers would be a partial solution for downstream +Sendmail systems. + +Using Postfix to block 0xff in message headers +============================================== + +One quick way to stop 0xff characters in message headers is to +specify a header_checks REGEXP pattern and action. Specifying +numerical character codes in REGEXP patterns turns out to be painful. +Here is a somewhat clumsy method to specify a 0xff matching REGEXP: + +perl -e 'print "/\xff/ REJECT Possible CA-2003-12 exploit\n"' > /etc/postfix/block255 + +/etc/postfix/main.cf: + header_checks = regexp:/etc/postfix/block255 ...other_files... + +The pattern was tested with FreeBSD 4, Redhat 8, Solaris 9, all on Intel. + +Raw binary data such as 0xff may cause trouble with text editors. +This is why the above example uses a separate file for blocking +the 0xff character instead of appending the pattern to an existing +header_checks file. + +The equivalent PCRE pattern may be easier to specify, but PCRE +support is not universally available with Postfix. diff --git a/postfix/HISTORY b/postfix/HISTORY index f0b02952b..8439d67f8 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -7926,8 +7926,51 @@ Apologies for any names omitted. Feature: the installation procedure records build information (by default: in /etc/postfix/makedefs.out). +20030324 + + Bugfix: smtp-source flushed too often, causing suboptimal + performance with smtp-source sending directly into smtp-sink. + Files: smtpstone/smtp-source.c. + +20030410 + + Safety: log a fatal error when a net/mask pattern has a + non-zero host part, so that mail delivery is deferred. + File: util/match_ops.c. + +20030411 + + Bugfix: extraneous warning about out-of-order original + recipient records by Patrik Rak. Files: *qmgr/qmgr_message.c. + +20030412 + + Workaround: log a warning and reset the queue file time + stamps when the file system clock is ahead of the local + clock. File: global/mail_stream.c. + +20030414 + + Feature: PostgreSQL client module, adopted by LaMont Jones. + Files: README_FILES/PGSQL_README, util/dict_pgsql.c, + util/dict_pgsql.h, conf/sample-pgsql-aliases.cf. + + Cleanup: the generic smtp client/server code in smtp_stream.c + now has an explicit flush operation, and the smtp-source/sink + programs are updated to take advantage of this. + + Cleanup: the file system clock drift detection code now runs + only once per process instance, to minimize the performance + impact. File: global/mail_stream.c. + + Robustness: avoid TIME_WAIT state with smtp/qmqp-source + client sockets. This puts less strain on local system + resources. + Open problems: + Low: smtp-source may block when sending large test messages. + Med: make qmgr recipient bounce/defer activity asynchronous or add a multi-recipient operation that reduces overhead. diff --git a/postfix/INSTALL b/postfix/INSTALL index b2361a488..b6a3465e9 100644 --- a/postfix/INSTALL +++ b/postfix/INSTALL @@ -144,15 +144,19 @@ configuration directory other than /etc/postfix, use: Be sure to get the quotes right. These details matter a lot. -Other parameters whose defaults can be specified in this way are: +Parameters whose defaults can be specified in this way are: - Macro name default value for - ------------------------------------- - DEF_COMMAND_DIR command_directory - DEF_DAEMON_DIR daemon_directory - DEF_SENDMAIL_PATH sendmail_path - DEF_MAILQ_PATH mailq_path - DEF_NEWALIAS_PATH newaliases_path + Macro name default value for typical default + ----------------------------------------------------------- + DEF_COMMAND_DIR command_directory /usr/sbin + DEF_CONFIG_DIR config_directory /etc/postfix + DEF_DAEMON_DIR daemon_directory /usr/libexec/postfix + DEF_MAILQ_PATH mailq_path /usr/bin/mailq + DEF_MANPAGE_DIR manpage_directory /usr/local/man + DEF_NEWALIAS_PATH newaliases_path /usr/bin/newaliases + DEF_README_DIR readme_directory no (do not install) + DEF_SAMPLE_DIR sample_directory /etc/postfix + DEF_SENDMAIL_PATH sendmail_path /usr/sbin/sendmail In order to build Postfix for very large applications, where you expect to run more than 1000 delivery processes, you may need to diff --git a/postfix/README_FILES/PGSQL_README b/postfix/README_FILES/PGSQL_README new file mode 100644 index 000000000..78675cee6 --- /dev/null +++ b/postfix/README_FILES/PGSQL_README @@ -0,0 +1,119 @@ +PostgreSQL map type for Postfix. Currently this code is maintained +by LaMont Jones, . + +This implementation allows for multiple pgsql databases: you can +use one for a virtual table, one for an access table, and one for +an aliases table if you want. + +You can specify multiple servers for the same database, so that +Postfix can switch to a good database server if one goes bad. + +Performance of postfix with pgsql has not been thoroughly tested, +however, we have found it to be stable. Busy mail servers using +pgsql maps will generate lots of concurrent pgsql clients, so the +pgsql server(s) should be run with this fact in mind. Any further +performance information, in addition to any feedback is most welcome. + +This is based upon code written by Scott Cotton and Joshua Marcus, +IC Group, Inc. The PostgreSQL changes were done by Aaron Sethman +. Updates for Postfix 1.1.x and PostgreSQL +7.1+, and support for calling stored procedures were added by Philip +Warner (pjw@rhyme.com.au). + +Building Postfix with PostgreSQL support +======================================== + +To use pgsql with Postfix on Debian GNU/Linux, you must install +the postfix-pgsql package. + +In order to build Postfix with pgsql map support, you will need to +add -DHAS_PGSQL and -I for the directory containing the PostgreSQL +header files and the libpq library to AUXLIBS, for example: + + make tidy + make -f Makefile.init makefiles \ + 'CCARGS=-DHAS_PGSQL -I/usr/local/include/pgsql' \ + 'AUXLIBS=-L/usr/local/lib -lpq' + +Then just run 'make'. + +Configuring PostgreSQL lookup tables +==================================== + +Once postfix is built with pgsql support, you can specify a map type +in main.cf like this: + +alias_maps = pgsql:/etc/postfix/pgsql-aliases.cf + +The file /etc/postfix/pgsql-aliases.cf specifies lots of information +telling postfix how to reference the pgsql database. An example +pgsql map config file follows: + +# +# pgsql config file for alias lookups on postfix +# comments are ok. +# + +# the user name and password to log into the pgsql server +user = someone +password = some_password + +# the database name on the servers +dbname = customer_database + +# the table name +table = mxaliases + +# these should be obvious :-) +select_field = forw_addr +where_field = alias + +# you may specify additional_conditions here +additional_conditions = and status = 'paid' + +# the above variables will result in a query of the form: +# +# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid' +# +# ($lookup is escaped so if it contains single quotes or other odd +# characters, it will not cause a parse error in the sql). + +# If you just want to use a PostgreSQL function, you can ignore the +# table name, select_field, where_field and additional_conditions, +# and just specify a database function to call: + +#select_function = my_lookup_user_alias + +# this will result in "select my_lookup_user_alias('name')" being +# used as the SQL statement to execute. If select_function is specified +# the table-related fields above will be ignored. +# +# As of 25-Jun-2002, if the function returns a single row and a single +# column AND that value is NULL, then the result will be treated as +# if the key was not in the dictionary. +# +# Future versions of PG will allow functions to return result sets. +# + +# +# the hosts that postfix will try to connect to +# and query from (in the order listed) +# specify unix: for unix-domain sockets, inet: for TCP connections (default) +hosts = host1.some.domain host2.some.domain unix:/file/name + +# end pgsql config file + +Eliminating single points of failure +==================================== + +Since sites that have a need for multiple mail exchangers may enjoy +the convenience of using a networked mailer database, but do not +want to introduce a single point of failure to their system, we've +included the ability to have postfix reference multiple hosts for +access to a single pgsql map. This will work if sites set up +mirrored pgsql databases on two or more hosts. Whenever queries +fail with an error at one host, the rest of the hosts will be tried +in order. Each host that is in an error state will undergo a +reconnection attempt every so often, and if no pgsql server hosts +are reachable, then mail will be deferred until at least one of +those hosts is reachable. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 55f25ed90..72b7e1905 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -22,6 +22,27 @@ 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.8-20040414 +========================================================= + +Too many people mess up their net/mask patterns, causing open +mail relay problems. Postfix processes now abort when given a +net/mask pattern with a non-zero host portion (for example, +168.100.189.2/28), and suggest to specify the proper net/mask +pattern instead (for example, 168.100.189.0/28). + +Major changes with Postfix snapshot 2.0.8-20040414 +================================================== + +PostgreSQL table lookups. Specify "pgsql:/file/name" where "/file/name" +defines the database. See the sample-pgsql-aliases.cf file for +examples, and the PGSQL_README file for general information. + +Workarounds for file systems whose clock runs ahead of the local +clock (this can happen with remote file systems). Postfix now logs +a warning and proceeds with reduced performance, instead of ignoring +new mail completely. + Incompatible changes with Postfix snapshot 2.0.6-20030305 ========================================================= diff --git a/postfix/US_PATENT_6321267 b/postfix/US_PATENT_6321267 new file mode 100644 index 000000000..4972090c2 --- /dev/null +++ b/postfix/US_PATENT_6321267 @@ -0,0 +1,124 @@ +1. Disclaimer: This text is not an authoritative statement. If +you are concerned about the implications of US patent 6,321,267, +then you should give this text to your own lawyer and get their +advice. + +1.1 Postfix is an MTA that aims to be an alternative to the widely + used Sendmail MTA. Postfix is available as open source code + from http://www.postfix.org/. One of the features implemented + by Postfix is called "sender address verification". + +1.2 US patent 6,321,267 (reference 4.1) describes a number of means + to stop junk email. One of the elements described in this + patent is called "active user testing". + +1.3 Postfix "sender address verification" and US patent 6,321,267 + "active user testing" are implemented by connecting to an MTA + that is responsible for the sender address. Specifically, both + use the SMTP RCPT command, and both infer the validity of the + address from the MTA's response. Reference 4.3 defines SMTP. + +===================================================================== + +2. It is my understanding that the Postfix MTA's "sender address +verification" does not infringe on US patent 6,321,267 for the +following reasons: + +2.1 There is prior art for US patent 6,321,267 "active user testing" + within the context of the Sendmail MTA. See item (3.1) below. + +2.2 US patent 6,321,267 covers "active user testing" only in + combination with functions that the Postfix MTA does not + implement. See items (3.2) through (3.5) below. + +===================================================================== + +3. Discussion of specific details of US patent 6,321,267, and their +relevance with respect to the Postfix MTA. + +3.1 Prior art. The "active user testing" method is described in + the paper "Selectively Rejecting SPAM Using Sendmail" by Robert + Harker (reference 4.2). The paper is cited as the first + reference in US patent 6,321,267, and was presented in October + 1997. The patent was filed more than two years later, in November + 1999. The paper says: + + Bogus User Address + + A desirable criterion for rejecting mail is to filter on + bogus user address. However, testing for a bad user address + is much harder because, short of sending a message to that + user address, there is no reliable way to check the validity + of the address. A simplistic test for a bad user address + might be to connect to the sender's SMTP server and use + either the SMTP VRFY or RCPT command to check the address. + If the server does local delivery of the message then this + would work well. + + The prior art is about stopping junk mail with the Sendmail + MTA. It is my understanding that this prior art is equally + applicable to other MTAs, including the Postfix MTA (see items + 1.1 and 2.2 above). + +3.2 Combination of elements not implemented by the Postfix MTA. + Claim 1 of US patent 6,321,267 involves a combination of A) + determining whether the sending system is a dialup host, B) + determining whether the sending system is an open mail relay, + and C) active user testing. + + Postfix does not implement elements A) and B) of claim 1. + Therefore, it is my understanding that the Postfix MTA does + not infringe on US patent 6,321,267 claim 1. + +3.3 Combination of elements not implemented by the Postfix MTA. + Claim 52 of US patent 6,321,267 involves the combination of A) + a proxy filter and B) active user testing. + + Postfix is an MTA, not a proxy, and does not implement element + A) of claim 52. Therefore, it is my understanding that the + Postfix MTA does not infringe on US patent 6,321,267 claim 52. + + US patent 6,321,267 makes a clear distinction between proxies + and MTAs. + + Figure 13 in US patent 6,321,267 shows how a proxy interacts + with a sending system and a local MTA. In the case of (sending + system, proxy, local MTA), the proxy assumes no responsibility + for delivery of the email message. The responsibility remains + with the sending system or passes directly to the local MTA. + + Figure 4 in US patent 6,321,267 shows how a sending system + interacts with an intermediate MTA. In the case of (sending + system, intermediate MTA, local MTA), the intermediate MTA + assumes full responsibility for delivery of the email message. + + Figure 2 in US patent 6,321,267 shows how a sending system + interacts with a local MTA. In the case of (sending system, + local MTA), the local MTA assumes full responsibility for + delivery of the email message. + +3.4 The other independent claims in US patent 6,321,267 involve + elements that the Postfix MTA does not implement, and do not + involve sender address verification. Therefore, it is my + understanding that the Postfix MTA does not infringe on these + claims in US patent 6,321,267. + +3.5 All dependent claims in US patent 6,321,267 depend on claims + that involve elements that the Postfix MTA does not implement. + Therefore, it is my understanding that the Postfix MTA does + not infringe on these claims in US patent 6,321,267. + +4.References: + +4.1 Albert L. Donaldson, "Method and apparatus for filtering junk + email", US patent 6,321,267. Filing date: November 23, 1999. + http://www.uspto.gov/ + +4.2 Robert Harker, "Selectively Rejecting SPAM Using Sendmail", + Proceedings of the Eleventh Systems Administration Conference + (LISA '97), San Diego, California, Oct. 1997, pp. 205-220. + http://www.usenix.org/publications/library/proceedings/lisa97/ + full_papers/22.harker/22.pdf + +4.3 Jonathan B. Postel, "Simple Mail Transfer Protocol", August + 1982. http://www.ietf.org/rfc.html diff --git a/postfix/conf/postfix-files b/postfix/conf/postfix-files index cb7ad74c3..b5175df76 100644 --- a/postfix/conf/postfix-files +++ b/postfix/conf/postfix-files @@ -163,6 +163,7 @@ $sample_directory/sample-misc.cf:f:root:-:644 $sample_directory/sample-pcre-access.cf:f:root:-:644 $sample_directory/sample-pcre-body.cf:f:root:-:644 $sample_directory/sample-pcre-header.cf:f:root:-:644 +$sample_directory/sample-pgsql-aliases.cf:f:root:-:644 $sample_directory/sample-qmqpd.cf:f:root:-:644 $sample_directory/sample-rate.cf:f:root:-:644 $sample_directory/sample-regexp-access.cf:f:root:-:644 diff --git a/postfix/conf/sample-misc.cf b/postfix/conf/sample-misc.cf index 2c83b8a34..0dca7c8e9 100644 --- a/postfix/conf/sample-misc.cf +++ b/postfix/conf/sample-misc.cf @@ -4,6 +4,13 @@ # This file contains example settings for miscellaneous Postfix # configuration parameters. +# The allow_min_user parameter specifies whether a recipient address +# can have a '-' as the first character. By default, this is not +# allowed, to avoid accidents with software that passes email addresses +# via the command line. +# +allow_min_user = no + # The always_bcc parameter specifies an optional address that # receives a copy of each message that enters the Postfix system, # not including bounces that are generated locally. diff --git a/postfix/conf/sample-pgsql-aliases.cf b/postfix/conf/sample-pgsql-aliases.cf new file mode 100644 index 000000000..a31cbccff --- /dev/null +++ b/postfix/conf/sample-pgsql-aliases.cf @@ -0,0 +1,51 @@ +# +# pgsql config file for alias lookups on postfix +# comments are ok. +# + +# the user name and password to log into the pgsql server +user = someone +password = some_password + +# the database name on the servers +dbname = customer_database + +# the table name +table = mxaliases + +# query components, see below +select_field = forw_addr +where_field = alias + +# you may specify additional_conditions here +additional_conditions = and status = 'paid' + +# the above variables will result in a query of the form: +# +# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid' +# +# ($lookup is escaped so if it contains single quotes or other odd +# characters, it will not cause a parse error in the sql). + +# If you just want to use a PostgreSQL function, you can ignore the +# table name, select_field, where_field and additional_conditions, +# and just specify a database function to call: + +#select_function = my_lookup_user_alias + +# this will result in "select my_lookup_user_alias('name')" being +# used as the SQL statement to execute. If select_function is specified +# the table-related fields above will be ignored. +# +# As of 25-Jun-2002, if the function returns a single row and a single +# column AND that value is NULL, then the result will be treated as +# if the key was not in the dictionary. +# +# Future versions of PG will allow functions to return result sets. +# + +# +# the hosts that postfix will try to connect to +# and query from (in the order listed) +# specify unix: for unix-domain sockets, inet: for TCP connections (default) +hosts = host1.some.domain host2.some.domain unix:/file/name diff --git a/postfix/html/basic.html b/postfix/html/basic.html index abaf63454..0e4262009 100644 --- a/postfix/html/basic.html +++ b/postfix/html/basic.html @@ -355,9 +355,10 @@ top-level domain).

My own networks

The mynetworks parameter lists all networks that this machine -somehow trusts. This information can be used by the -anti-UCE features to recognize trusted SMTP clients that are -allowed to relay mail through Postfix. +somehow trusts. This information can be used by the anti-UCE features +to recognize trusted SMTP clients that are allowed to relay mail +through Postfix.

diff --git a/postfix/html/virtual.5.html b/postfix/html/virtual.5.html index 2f855eac8..30011e065 100644 --- a/postfix/html/virtual.5.html +++ b/postfix/html/virtual.5.html @@ -138,9 +138,9 @@ VIRTUAL(5) VIRTUAL(5) user2@virtual-alias.domain address2, address3 The virtual-alias.domain anything entry is required for a - virtual alias domain. Without this entry, mail is rejected - with "relay access denied", or bounces with "mail loops - back to myself". + virtual alias domain. Without this entry, mail is rejected + with "relay access denied", or bounces with "mail loops + back to myself". Do not specify virtual alias domain names in the main.cf mydestination or relay_domains configuration parameters. diff --git a/postfix/man/man1/smtp-sink.1 b/postfix/man/man1/smtp-sink.1 index 3335f076f..05e522ef0 100644 --- a/postfix/man/man1/smtp-sink.1 +++ b/postfix/man/man1/smtp-sink.1 @@ -41,7 +41,7 @@ Terminate after \fIcount\fR sessions. This is for testing purposes. Disable ESMTP command pipelining. .IP \fB-P\fR Change the server greeting so that it appears to come through -a CISCO PIX system. +a CISCO PIX system. Implies \fB-e\fR. .IP "\fB-s \fIcommand,command,...\fR" Log the named commands to syslogd. Examples of commands that can be logged are HELO, EHLO, LHLO, MAIL, diff --git a/postfix/man/man5/virtual.5 b/postfix/man/man5/virtual.5 index 98a64d047..bec609898 100644 --- a/postfix/man/man5/virtual.5 +++ b/postfix/man/man5/virtual.5 @@ -141,9 +141,9 @@ See the output from \fBpostconf -m\fR for available database types. .fi .sp The \fIvirtual-alias.domain anything\fR entry is required for a -virtual alias domain. Without this entry, mail is rejected +virtual alias domain. \fBWithout this entry, mail is rejected with "relay access denied", or bounces with -"mail loops back to myself". +"mail loops back to myself".\fR Do not specify virtual alias domain names in the \fBmain.cf mydestination\fR or \fBrelay_domains\fR configuration parameters. diff --git a/postfix/postfix-install b/postfix/postfix-install index 6bb2044b2..0bb3b3523 100644 --- a/postfix/postfix-install +++ b/postfix/postfix-install @@ -81,36 +81,37 @@ # The built-in default directory name is the current directory. # This parameter setting is not recorded in the installed main.cf file. # .IP config_directory -# The destination directory for Postfix configuration files. +# The final destination directory for Postfix configuration files. # The built-in default directory name is /etc/postfix. -# This parameter setting is not recorded in the installed main.cf file. +# This parameter setting is not recorded in the installed main.cf file +# and can be changed only by recompiling Postfix. # .IP daemon_directory -# The destination directory for Postfix daemon programs. This directory -# should not be in the command search path of any users. +# The final destination directory for Postfix daemon programs. This +# directory should not be in the command search path of any users. # The built-in default directory name is /usr/libexec/postfix. # This parameter setting is recorded in the installed main.cf file. # .IP command_directory -# The destination directory for Postfix administrative commands. This -# directory should be in the command search path of adminstrative users. -# The built-in default directory name is system dependent. +# The final destination directory for Postfix administrative commands. +# This directory should be in the command search path of adminstrative +# users. The built-in default directory name is system dependent. # This parameter setting is recorded in the installed main.cf file. # .IP queue_directory -# The destination directory for Postfix queues. +# The final destination directory for Postfix queues. # The built-in default directory name is /var/spool/postfix. # This parameter setting is recorded in the installed main.cf file. # .IP sendmail_path -# The full destination pathname for the Postfix sendmail command. +# The final destination pathname for the Postfix sendmail command. # This is the Sendmail-compatible mail posting interface. # The built-in default pathname is system dependent. # This parameter setting is recorded in the installed main.cf file. # .IP newaliases_path -# The full destination pathname for the Postfix newaliases command. +# The final destination pathname for the Postfix newaliases command. # This is the Sendmail-compatible command to build alias databases # for the Postfix local delivery agent. # The built-in default pathname is system dependent. # This parameter setting is recorded in the installed main.cf file. # .IP mailq_path -# The full destination pathname for the Postfix mailq command. +# The final destination pathname for the Postfix mailq command. # This is the Sendmail-compatible command to list the mail queue. # The built-in default pathname is system dependent. # This parameter setting is recorded in the installed main.cf file. @@ -286,30 +287,30 @@ distribution to other machines." tempdir_prompt="a directory for scratch files while installing Postfix. You must have write permission in this directory." -config_directory_prompt="the destination directory for installed -Postfix configuration files." +config_directory_prompt="the final destination directory for +installed Postfix configuration files." -daemon_directory_prompt="the destination directory for installed -Postfix daemon programs. This directory should not be in the -command search path of any users." +daemon_directory_prompt="the final destination directory for +installed Postfix daemon programs. This directory should not be +in the command search path of any users." -command_directory_prompt="the destination directory for installed -Postfix administrative commands. This directory should be in the -command search path of adminstrative users." +command_directory_prompt="the final destination directory for +installed Postfix administrative commands. This directory should +be in the command search path of adminstrative users." -queue_directory_prompt="the destination directory for Postfix +queue_directory_prompt="the final destination directory for Postfix queues." -sendmail_path_prompt="the full destination pathname for the installed -Postfix sendmail command. This is the Sendmail-compatible mail -posting interface." +sendmail_path_prompt="the final destination pathname for the +installed Postfix sendmail command. This is the Sendmail-compatible +mail posting interface." -newaliases_path_prompt="the full destination pathname for the +newaliases_path_prompt="the final destination pathname for the installed Postfix newaliases command. This is the Sendmail-compatible command to build alias databases for the Postfix local delivery agent." -mailq_path_prompt="the full destination pathname for the installed +mailq_path_prompt="the final destination pathname for the installed Postfix mailq command. This is the Sendmail-compatible mail queue listing command." diff --git a/postfix/proto/virtual b/postfix/proto/virtual index 5c559a15a..f0d904aa0 100644 --- a/postfix/proto/virtual +++ b/postfix/proto/virtual @@ -129,9 +129,9 @@ # .fi # .sp # The \fIvirtual-alias.domain anything\fR entry is required for a -# virtual alias domain. Without this entry, mail is rejected +# virtual alias domain. \fBWithout this entry, mail is rejected # with "relay access denied", or bounces with -# "mail loops back to myself". +# "mail loops back to myself".\fR # # Do not specify virtual alias domain names in the \fBmain.cf # mydestination\fR or \fBrelay_domains\fR configuration parameters. diff --git a/postfix/src/global/mail_stream.c b/postfix/src/global/mail_stream.c index 6e4812e03..e07e37d59 100644 --- a/postfix/src/global/mail_stream.c +++ b/postfix/src/global/mail_stream.c @@ -83,6 +83,7 @@ #include #include #include +#include /* Utility library. */ @@ -125,6 +126,12 @@ static int mail_stream_finish_file(MAIL_STREAM * info, VSTRING *unused_why) { int status = 0; static char wakeup[] = {TRIGGER_REQ_WAKEUP}; + struct stat st; + time_t now; + struct utimbuf tbuf; + char *queue_file_path = 0; + static int fs_clock_ok = 0; + static int fs_clock_warned = 0; /* * Make sure the message makes it to file. Set the execute bit when no @@ -137,15 +144,46 @@ static int mail_stream_finish_file(MAIL_STREAM * info, VSTRING *unused_why) * as are files with unknown record type codes. Every Postfix queue file * must end with an explicit END record. Postfix queue files without END * record are discarded. + * + * Attempt to detect file system clocks that are ahead of local time. the + * effect can be difficult to understand (mail is enqueued but Postfix + * ignores it). This clock drift detection may not work with file systems + * that work on a local copy of the file and that update the server only + * after the file is closed. */ if (vstream_fflush(info->stream) || fchmod(vstream_fileno(info->stream), 0700 | info->mode) #ifdef HAS_FSYNC || fsync(vstream_fileno(info->stream)) #endif + || (fs_clock_ok == 0 && fstat(vstream_fileno(info->stream), &st) < 0) ) status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE); +#ifdef TEST + st.st_mtime += 10; +#endif + + /* + * Don't check the file system clock all the time. + */ + if (fs_clock_ok == 0 && st.st_mtime <= time(&now)) + fs_clock_ok = 1; + + /* + * Work around file system clocks that are ahead of local time. + */ + if (status == CLEANUP_STAT_OK && fs_clock_ok == 0) { + if (fs_clock_warned == 0) { + msg_warn("%s: file system clock is %d seconds ahead of local clock", + info->id, (int) (st.st_mtime - now)); + msg_warn("%s: resetting file time stamps - this hurts performance", + info->id); + fs_clock_warned = 1; + } + queue_file_path = mystrdup(VSTREAM_PATH(info->stream)); + } + /* * Close the queue file and mark it as closed. Be prepared for * vstream_fclose() to fail even after vstream_fflush() and fsync() @@ -158,6 +196,16 @@ static int mail_stream_finish_file(MAIL_STREAM * info, VSTRING *unused_why) status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE); info->stream = 0; + /* + * Work around file system clocks that are ahead of local time. + */ + if (queue_file_path != 0) { + tbuf.actime = tbuf.modtime = now; + if (utime(queue_file_path, &tbuf) < 0 && errno != ENOENT) + msg_fatal("%s: update file time stamps: %m", info->id); + myfree(queue_file_path); + } + /* * When all is well, notify the next service that a new message has been * queued. diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 0b847dec6..4a74a4f8b 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 "20030319" +#define MAIL_RELEASE_DATE "20030414" #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "2.0.7-" MAIL_RELEASE_DATE +#define DEF_MAIL_VERSION "2.0.8-" MAIL_RELEASE_DATE extern char *var_mail_version; /* diff --git a/postfix/src/global/rewrite_clnt.c b/postfix/src/global/rewrite_clnt.c index 2d7606535..a2cf24cd2 100644 --- a/postfix/src/global/rewrite_clnt.c +++ b/postfix/src/global/rewrite_clnt.c @@ -79,7 +79,6 @@ static VSTRING *last_result; VSTRING *rewrite_clnt(const char *rule, const char *addr, VSTRING *result) { - char *myname = "rewrite_clnt"; VSTREAM *stream; /* diff --git a/postfix/src/global/smtp_stream.c b/postfix/src/global/smtp_stream.c index 751e92aa6..c2ab8ed26 100644 --- a/postfix/src/global/smtp_stream.c +++ b/postfix/src/global/smtp_stream.c @@ -14,6 +14,9 @@ /* VSTREAM *stream; /* const char *format; /* +/* void smtp_flush(stream) +/* VSTREAM *stream; +/* /* int smtp_get(vp, stream, maxlen) /* VSTRING *vp; /* VSTREAM *stream; @@ -53,9 +56,11 @@ /* The stream is configured to enable exception handling. /* .PP /* smtp_printf() formats its arguments and writes the result to -/* the named stream, followed by a CR LF pair. The stream is flushed. +/* the named stream, followed by a CR LF pair. The stream is NOT flushed. /* Long lines of text are not broken. /* +/* smtp_flush() flushes the named stream. +/* /* smtp_get() reads the named stream up to and including /* the next LF character and strips the trailing CR LF. The /* \fImaxlen\fR argument limits the length of a line of text, @@ -152,6 +157,29 @@ void smtp_timeout_setup(VSTREAM *stream, int maxtime) VSTREAM_CTL_END); } +/* smtp_flush - flush stream */ + +void smtp_flush(VSTREAM *stream) +{ + int err; + + /* + * Do the I/O, protected against timeout. + */ + smtp_timeout_reset(stream); + err = vstream_fflush(stream); + smtp_timeout_detect(stream); + + /* + * See if there was a problem. + */ + if (err != 0) { + if (msg_verbose) + msg_info("smtp_flush: EOF"); + vstream_longjmp(stream, SMTP_ERR_EOF); + } +} + /* smtp_vprintf - write one line to SMTP peer */ void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap) @@ -164,7 +192,7 @@ void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap) smtp_timeout_reset(stream); vstream_vfprintf(stream, fmt, ap); vstream_fputs("\r\n", stream); - err = vstream_fflush(stream); + err = vstream_ferror(stream); smtp_timeout_detect(stream); /* diff --git a/postfix/src/global/smtp_stream.h b/postfix/src/global/smtp_stream.h index 09f53e7bc..29efecf1e 100644 --- a/postfix/src/global/smtp_stream.h +++ b/postfix/src/global/smtp_stream.h @@ -31,6 +31,7 @@ extern void smtp_timeout_setup(VSTREAM *, int); extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...); +extern void smtp_flush(VSTREAM *); extern int smtp_get(VSTRING *, VSTREAM *, int); extern void smtp_fputs(const char *, int len, VSTREAM *); extern void smtp_fwrite(const char *, int len, VSTREAM *); diff --git a/postfix/src/nqmgr/qmgr_message.c b/postfix/src/nqmgr/qmgr_message.c index 3be89bfcf..04c52243c 100644 --- a/postfix/src/nqmgr/qmgr_message.c +++ b/postfix/src/nqmgr/qmgr_message.c @@ -404,6 +404,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message) message->rcpt_unread--; } } else if (rec_type == REC_TYPE_RCPT) { + /* See also below for code setting orig_rcpt. */ if (message->rcpt_list.len < recipient_limit) { message->rcpt_unread--; qmgr_rcpt_list_add(&message->rcpt_list, curr_offset, @@ -478,7 +479,9 @@ static int qmgr_message_read(QMGR_MESSAGE *message) orig_rcpt = 0; } if (rec_type == REC_TYPE_ORCP) - orig_rcpt = mystrdup(start); + /* See also above for code clearing orig_rcpt. */ + if (message->rcpt_offset == 0) + orig_rcpt = mystrdup(start); } while (rec_type > 0 && rec_type != REC_TYPE_END); /* diff --git a/postfix/src/qmgr/qmgr_message.c b/postfix/src/qmgr/qmgr_message.c index f3bc58e49..eea77dd64 100644 --- a/postfix/src/qmgr/qmgr_message.c +++ b/postfix/src/qmgr/qmgr_message.c @@ -280,6 +280,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message) "queue %s", message->queue_name); } } else if (rec_type == REC_TYPE_RCPT) { + /* See also below for code setting orig_rcpt. */ #define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0)) if (message->rcpt_list.len < FUDGE(var_qmgr_rcpt_limit)) { qmgr_rcpt_list_add(&message->rcpt_list, curr_offset, @@ -358,7 +359,9 @@ static int qmgr_message_read(QMGR_MESSAGE *message) orig_rcpt = 0; } if (rec_type == REC_TYPE_ORCP) - orig_rcpt = mystrdup(start); + /* See also above for code clearing orig_rcpt. */ + if (message->rcpt_offset == 0) + orig_rcpt = mystrdup(start); } while (rec_type > 0 && rec_type != REC_TYPE_END); /* diff --git a/postfix/src/smtpstone/qmqp-source.c b/postfix/src/smtpstone/qmqp-source.c index 66fd33854..b2beb0202 100644 --- a/postfix/src/smtpstone/qmqp-source.c +++ b/postfix/src/smtpstone/qmqp-source.c @@ -293,6 +293,7 @@ static void fail_connect(SESSION *session) static void start_connect(SESSION *session) { int fd; + struct linger linger; /* * Some systems don't set the socket error when connect() fails early @@ -303,6 +304,11 @@ static void start_connect(SESSION *session) if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) msg_fatal("socket: %m"); (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); session->stream = vstream_fdopen(fd, O_RDWR); event_enable_write(fd, connect_done, (char *) session); netstring_setup(session->stream, var_timeout); diff --git a/postfix/src/smtpstone/smtp-sink.c b/postfix/src/smtpstone/smtp-sink.c index ac3fac7f5..cdcd1cd05 100644 --- a/postfix/src/smtpstone/smtp-sink.c +++ b/postfix/src/smtpstone/smtp-sink.c @@ -35,7 +35,7 @@ /* Disable ESMTP command pipelining. /* .IP \fB-P\fR /* Change the server greeting so that it appears to come through -/* a CISCO PIX system. +/* a CISCO PIX system. Implies \fB-e\fR. /* .IP "\fB-s \fIcommand,command,...\fR" /* Log the named commands to syslogd. /* Examples of commands that can be logged are HELO, EHLO, LHLO, MAIL, @@ -146,6 +146,7 @@ static void ehlo_response(SINK_STATE *state) if (!disable_8bitmime) smtp_printf(state->stream, "250-8BITMIME"); smtp_printf(state->stream, "250 "); + smtp_flush(state->stream); } /* helo_response - respond to HELO command */ @@ -153,6 +154,7 @@ static void ehlo_response(SINK_STATE *state) static void helo_response(SINK_STATE *state) { smtp_printf(state->stream, "250 %s", var_myhostname); + smtp_flush(state->stream); } /* ok_response - send 250 OK */ @@ -160,6 +162,7 @@ static void helo_response(SINK_STATE *state) static void ok_response(SINK_STATE *state) { smtp_printf(state->stream, "250 Ok"); + smtp_flush(state->stream); } /* mail_response - reset recipient count, send 250 OK */ @@ -184,6 +187,7 @@ static void data_response(SINK_STATE *state) { state->data_state = ST_CR_LF; smtp_printf(state->stream, "354 End data with ."); + smtp_flush(state->stream); state->read = data_read; } @@ -213,6 +217,7 @@ static void dot_response(SINK_STATE *state) static void quit_response(SINK_STATE *state) { smtp_printf(state->stream, "221 Bye"); + smtp_flush(state->stream); if (count) { counter++; vstream_printf("%d\r", counter); @@ -418,6 +423,7 @@ static int command_read(SINK_STATE *state) ptr = vstring_str(state->buffer); if ((command = mystrtok(&ptr, " \t")) == 0) { smtp_printf(state->stream, "500 Error: unknown command"); + smtp_flush(state->stream); return (0); } if (msg_verbose) @@ -427,6 +433,7 @@ static int command_read(SINK_STATE *state) break; if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) { smtp_printf(state->stream, "500 Error: unknown command"); + smtp_flush(state->stream); return (0); } /* We use raw syslog. Sanitize data content and length. */ @@ -522,6 +529,7 @@ static void connect_event(int unused_event, char *context) smtp_printf(state->stream, "220 %s", var_myhostname); else smtp_printf(state->stream, "220 %s ESMTP", var_myhostname); + smtp_flush(state->stream); event_enable_read(fd, read_event, (char *) state); } } @@ -569,6 +577,7 @@ int main(int argc, char **argv) break; case 'P': pretend_pix = 1; + disable_esmtp = 1; break; case 's': openlog(basename(argv[0]), LOG_PID, LOG_MAIL); diff --git a/postfix/src/smtpstone/smtp-source.c b/postfix/src/smtpstone/smtp-source.c index b5aeaddc7..c575c5a34 100644 --- a/postfix/src/smtpstone/smtp-source.c +++ b/postfix/src/smtpstone/smtp-source.c @@ -214,6 +214,7 @@ static void command(VSTREAM *stream, char *fmt,...) va_start(ap, fmt); smtp_vprintf(stream, fmt, ap); va_end(ap); + smtp_flush(stream); } /* socket_error - look up and reset the last socket error */ @@ -398,6 +399,7 @@ static void fail_connect(SESSION *session) static void start_connect(SESSION *session) { int fd; + struct linger linger; /* * Some systems don't set the socket error when connect() fails early @@ -408,6 +410,11 @@ static void start_connect(SESSION *session) if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) msg_fatal("socket: %m"); (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); session->stream = vstream_fdopen(fd, O_RDWR); event_enable_write(fd, connect_done, (char *) session); smtp_timeout_setup(session->stream, var_timeout); @@ -672,6 +679,11 @@ static void data_done(int unused_event, char *context) smtp_fputs("La de da de da 3.", 17, session->stream); smtp_fputs("La de da de da 4.", 17, session->stream); } else { + + /* + * XXX This may cause the process to block with message content + * larger than VSTREAM_BUFIZ bytes. + */ smtp_fputs(message_data, message_length, session->stream); } diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 4167dfff0..19c863f43 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -4,75 +4,75 @@ SRCS = alldig.c argv.c argv_split.c attr_print0.c attr_print64.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_nisplus.c dict_open.c dict_pcre.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 fifo_trigger.c \ - file_limit.c find_inet.c fsspace.c fullname.c get_domainname.c \ - get_hostname.c hex_quote.c htable.c inet_addr_host.c \ - inet_addr_list.c inet_addr_local.c inet_connect.c inet_listen.c \ - inet_trigger.c inet_util.c intv.c line_wrap.c lowercase.c \ - lstat_as.c mac_expand.c mac_parse.c make_dirs.c match_list.c \ - match_ops.c msg.c msg_output.c msg_syslog.c msg_vstream.c \ - mvect.c myflock.c mymalloc.c myrand.c mystrtok.c name_mask.c \ - netstring.c non_blocking.c open_as.c open_limit.c open_lock.c \ - peekfd.c percentm.c posix_signals.c printable.c rand_sleep.c \ - read_wait.c readable.c readlline.c ring.c safe_getenv.c \ - safe_open.c sane_accept.c sane_link.c sane_rename.c \ - sane_socketpair.c sane_time.c scan_dir.c set_eugid.c set_ugid.c \ - sigdelay.c skipblanks.c spawn_command.c split_at.c \ - split_nameval.c stat_as.c stream_connect.c stream_listen.c \ - stream_trigger.c sys_compat.c timed_connect.c timed_read.c \ - timed_wait.c timed_write.c translit.c trimblanks.c unescape.c \ - unix_connect.c unix_listen.c unix_trigger.c unsafe.c username.c \ - valid_hostname.c vbuf.c vbuf_print.c vstream.c vstream_popen.c \ - vstring.c vstring_vstream.c watchdog.c writable.c write_buf.c \ - write_wait.c strcasecmp.c nvtable.c host_port.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 \ + fifo_trigger.c file_limit.c find_inet.c fsspace.c fullname.c \ + get_domainname.c get_hostname.c hex_quote.c host_port.c htable.c \ + inet_addr_host.c inet_addr_list.c inet_addr_local.c inet_connect.c \ + inet_listen.c inet_trigger.c inet_util.c intv.c line_wrap.c \ + lowercase.c lstat_as.c mac_expand.c mac_parse.c make_dirs.c \ + match_list.c match_ops.c msg.c msg_output.c msg_syslog.c \ + msg_vstream.c mvect.c myflock.c mymalloc.c myrand.c mystrtok.c \ + name_mask.c netstring.c non_blocking.c nvtable.c open_as.c \ + open_limit.c open_lock.c peekfd.c percentm.c posix_signals.c \ + printable.c rand_sleep.c read_wait.c readable.c readlline.c \ + ring.c safe_getenv.c safe_open.c sane_accept.c sane_link.c \ + sane_rename.c sane_socketpair.c sane_time.c scan_dir.c \ + set_eugid.c set_ugid.c sigdelay.c skipblanks.c spawn_command.c \ + split_at.c split_nameval.c stat_as.c strcasecmp.c stream_connect.c \ + stream_listen.c stream_trigger.c sys_compat.c timed_connect.c \ + timed_read.c timed_wait.c timed_write.c translit.c trimblanks.c \ + unescape.c unix_connect.c unix_listen.c unix_trigger.c unsafe.c \ + username.c valid_hostname.c vbuf.c vbuf_print.c vstream.c \ + vstream_popen.c vstring.c vstring_vstream.c watchdog.c writable.c \ + write_buf.c write_wait.c 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_nisplus.o dict_open.o dict_pcre.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 fifo_trigger.o \ - file_limit.o find_inet.o fsspace.o fullname.o get_domainname.o \ - get_hostname.o hex_quote.o htable.o inet_addr_host.o \ - inet_addr_list.o inet_addr_local.o inet_connect.o inet_listen.o \ - inet_trigger.o inet_util.o intv.o line_wrap.o lowercase.o \ - lstat_as.o mac_expand.o mac_parse.o make_dirs.o match_list.o \ - match_ops.o msg.o msg_output.o msg_syslog.o msg_vstream.o \ - mvect.o myflock.o mymalloc.o myrand.o mystrtok.o name_mask.o \ - netstring.o non_blocking.o open_as.o open_limit.o open_lock.o \ - peekfd.o percentm.o posix_signals.o printable.o rand_sleep.o \ - read_wait.o readable.o readlline.o ring.o safe_getenv.o \ - safe_open.o sane_accept.o sane_link.o sane_rename.o \ - sane_socketpair.o sane_time.o scan_dir.o set_eugid.o set_ugid.o \ - sigdelay.o skipblanks.o spawn_command.o split_at.o \ - split_nameval.o stat_as.o stream_connect.o stream_listen.o \ - stream_trigger.o sys_compat.o timed_connect.o timed_read.o \ - timed_wait.o timed_write.o translit.o trimblanks.o unescape.o \ - unix_connect.o unix_listen.o unix_trigger.o unsafe.o username.o \ - valid_hostname.o vbuf.o vbuf_print.o vstream.o vstream_popen.o \ - vstring.o vstring_vstream.o watchdog.o writable.o write_buf.o \ - write_wait.o nvtable.o $(STRCASE) host_port.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 \ + fifo_trigger.o file_limit.o find_inet.o fsspace.o fullname.o \ + get_domainname.o get_hostname.o hex_quote.o host_port.o htable.o \ + inet_addr_host.o inet_addr_list.o inet_addr_local.o inet_connect.o \ + inet_listen.o inet_trigger.o inet_util.o intv.o line_wrap.o \ + lowercase.o lstat_as.o mac_expand.o mac_parse.o make_dirs.o \ + match_list.o match_ops.o msg.o msg_output.o msg_syslog.o \ + msg_vstream.o mvect.o myflock.o mymalloc.o myrand.o mystrtok.o \ + name_mask.o netstring.o non_blocking.o nvtable.o open_as.o \ + open_limit.o open_lock.o peekfd.o percentm.o posix_signals.o \ + printable.o rand_sleep.o read_wait.o readable.o readlline.o \ + ring.o safe_getenv.o safe_open.o sane_accept.o sane_link.o \ + sane_rename.o sane_socketpair.o sane_time.o scan_dir.o \ + set_eugid.o set_ugid.o sigdelay.o skipblanks.o spawn_command.o \ + split_at.o split_nameval.o stat_as.o stream_connect.o \ + stream_listen.o stream_trigger.o sys_compat.o timed_connect.o \ + timed_read.o timed_wait.o timed_write.o translit.o trimblanks.o \ + unescape.o unix_connect.o unix_listen.o unix_trigger.o unsafe.o \ + username.o valid_hostname.o vbuf.o vbuf_print.o vstream.o \ + vstream_popen.o vstring.o vstring_vstream.o watchdog.o writable.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_nisplus.h dict_pcre.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 get_hostname.h hex_quote.h \ - htable.h inet_addr_host.h inet_addr_list.h inet_addr_local.h \ - inet_util.h intv.h iostuff.h line_wrap.h listen.h lstat_as.h \ - mac_expand.h mac_parse.h make_dirs.h match_list.h match_ops.h \ - msg.h msg_output.h msg_syslog.h msg_vstream.h mvect.h myflock.h \ - mymalloc.h myrand.h name_mask.h netstring.h open_as.h open_lock.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 \ + get_hostname.h hex_quote.h host_port.h htable.h inet_addr_host.h \ + inet_addr_list.h inet_addr_local.h inet_util.h intv.h iostuff.h \ + line_wrap.h listen.h lstat_as.h mac_expand.h mac_parse.h \ + make_dirs.h match_list.h match_ops.h msg.h msg_output.h \ + msg_syslog.h msg_vstream.h mvect.h myflock.h mymalloc.h myrand.h \ + name_mask.h netstring.h nvtable.h open_as.h open_lock.h \ percentm.h posix_signals.h readlline.h ring.h safe.h safe_open.h \ sane_accept.h sane_fsops.h sane_socketpair.h sane_time.h \ scan_dir.h set_eugid.h set_ugid.h sigdelay.h spawn_command.h \ split_at.h stat_as.h stringops.h sys_defs.h timed_connect.h \ timed_wait.h trigger.h username.h valid_hostname.h vbuf.h \ - vbuf_print.h vstream.h vstring.h vstring_vstream.h watchdog.h \ - nvtable.h host_port.h + vbuf_print.h vstream.h vstring.h vstring_vstream.h watchdog.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ stream_test.c dup2_pass_on_exec.c WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ @@ -615,6 +615,7 @@ dict_open.o: dict_nisplus.h dict_open.o: dict_ni.h dict_open.o: dict_ldap.h dict_open.o: dict_mysql.h +dict_open.o: dict_pgsql.h dict_open.o: dict_pcre.h dict_open.o: dict_regexp.h dict_open.o: dict_static.h @@ -636,6 +637,8 @@ dict_pcre.o: dict.h dict_pcre.o: argv.h dict_pcre.o: dict_pcre.h dict_pcre.o: mac_parse.h +dict_pgsql.o: dict_pgsql.c +dict_pgsql.o: sys_defs.h dict_regexp.o: dict_regexp.c dict_regexp.o: sys_defs.h dict_regexp.o: mymalloc.h diff --git a/postfix/src/util/dict_open.c b/postfix/src/util/dict_open.c index 1ce24cb9b..0e01366f2 100644 --- a/postfix/src/util/dict_open.c +++ b/postfix/src/util/dict_open.c @@ -174,6 +174,7 @@ #include #include #include +#include #include #include #include @@ -217,6 +218,9 @@ static DICT_OPEN_INFO dict_open_info[] = { #ifdef HAS_MYSQL DICT_TYPE_MYSQL, dict_mysql_open, #endif +#ifdef HAS_PGSQL + DICT_TYPE_PGSQL, dict_pgsql_open, +#endif #ifdef HAS_PCRE DICT_TYPE_PCRE, dict_pcre_open, #endif diff --git a/postfix/src/util/dict_pgsql.c b/postfix/src/util/dict_pgsql.c new file mode 100644 index 000000000..0fc350f67 --- /dev/null +++ b/postfix/src/util/dict_pgsql.c @@ -0,0 +1,712 @@ +/*++ +/* NAME +/* dict_pgsql 3 +/* SUMMARY +/* dictionary manager interface to Postgresql files +/* SYNOPSIS +/* #include +/* +/* DICT *dict_pgsql_open(name, dummy, unused_dict_flags) +/* const char *name; +/* int dummy; +/* int unused_dict_flags; +/* DESCRIPTION +/* dict_pgsql_open() creates a dictionary of type 'pgsql'. This +/* dictionary is an interface for the postfix key->value mappings +/* to pgsql. The result is a pointer to the installed dictionary, +/* or a null pointer in case of problems. +/* +/* The pgsql dictionary can manage multiple connections to +/* different sql servers on different hosts. It assumes that +/* the underlying data on each host is identical (mirrored) and +/* maintains one connection at any given time. If any connection +/* fails, any other available ones will be opened and used. +/* The intent of this feature is to eliminate a single point of +/* failure for mail systems that would otherwise rely on a single +/* pgsql server. +/* +/* Arguments: +/* .IP name +/* The path of the PostgreSQL configuration file. The file +/* encodes number of pieces of information: username, password, +/* databasename, table, select_field, where_field, and hosts. +/* For example, if you want the map to reference databases of +/* the name "your_db" and execute a query like this: select +/* forw_addr from aliases where alias like '' +/* against any database called "postfix_info" located on hosts +/* host1.some.domain and host2.some.domain, logging in as user +/* "postfix" and password "passwd" then the configuration file +/* should read: +/* +/* user = postfix +/* password = passwd +/* DBname = postfix_info +/* table = aliases +/* select_field = forw_addr +/* where_field = alias +/* hosts = host1.some.domain host2.some.domain +/* +/* .IP other_name +/* reference for outside use. +/* .IP unusued_flags +/* unused flags +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Aaron Sethman +/* androsyn@ratbox.org +/* +/* Based upon dict_mysql.c by +/* +/* Scott Cotton +/* IC Group, Inc. +/* scott@icgroup.com +/* +/* Joshua Marcus +/* IC Group, Inc. +/* josh@icgroup.com +/*--*/ + +/* System library. */ +#include "sys_defs.h" + +#ifdef HAS_PGSQL +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Utility library. */ +#include "dict.h" +#include "msg.h" +#include "mymalloc.h" +#include "dict_pgsql.h" +#include "argv.h" +#include "vstring.h" +#include "split_at.h" +#include "find_inet.h" + +#define STATACTIVE 0 +#define STATFAIL 1 +#define STATUNTRIED 2 +#define RETRY_CONN_INTV 60 /* 1 minute */ + +typedef struct { + PGconn *db; + char *hostname; + int stat; /* STATUNTRIED | STATFAIL | STATCUR */ + time_t ts; /* used for attempting reconnection */ +} HOST; + +typedef struct { + int len_hosts; /* number of hosts */ + HOST *db_hosts; /* hosts on which databases reside */ +} PLPGSQL; + +typedef struct { + char *username; + char *password; + char *dbname; + char *table; + char *query; /* if set, overrides fields, etc */ + char *select_function; + char *select_field; + char *where_field; + char *additional_conditions; + char **hostnames; + int len_hosts; +} PGSQL_NAME; + +typedef struct { + DICT dict; + PLPGSQL *pldb; + PGSQL_NAME *name; +} DICT_PGSQL; + + +/* Just makes things a little easier for me.. */ +#define PGSQL_RES PGresult + +/* internal function declarations */ +static PLPGSQL *plpgsql_init(char *hostnames[], int); +static PGSQL_RES *plpgsql_query(PLPGSQL *, const char *, char *, char *, char *); +static void plpgsql_dealloc(PLPGSQL *); +static void plpgsql_down_host(HOST *); +static void plpgsql_connect_single(HOST *, char *, char *, char *); +static const char *dict_pgsql_lookup(DICT *, const char *); +DICT *dict_pgsql_open(const char *, int, int); +static void dict_pgsql_close(DICT *); +static PGSQL_NAME *pgsqlname_parse(const char *); +static HOST host_init(char *); + + + +/********************************************************************** + * public interface dict_pgsql_lookup + * find database entry return 0 if no alias found, set dict_errno + * on errors to DICT_ERROR_RETRY and set dict_errno to 0 on success + *********************************************************************/ +static void pgsql_escape_string(char *new, const char *old, unsigned int len) +{ + unsigned int x, + y; + + /* + * XXX We really should be using an escaper that is provided by the PGSQL + * library. The code below seems to be over-kill (see RUS-CERT Advisory + * 2001-08:01), but it's better to be safe than to be sorry -- Wietse + */ + for (x = 0, y = 0; x < len; x++, y++) { + switch (old[x]) { + case '\n': + new[y++] = '\\'; + new[y] = 'n'; + break; + case '\r': + new[y++] = '\\'; + new[y] = 'r'; + break; + case '\'': + new[y++] = '\\'; + new[y] = '\''; + break; + case '"': + new[y++] = '\\'; + new[y] = '"'; + break; + case 0: + new[y++] = '\\'; + new[y] = '0'; + break; + default: + new[y] = old[x]; + break; + } + } + new[y] = 0; +} + +/* + * expand a filter (lookup or result) + */ +static void dict_pgsql_expand_filter(char *filter, char *value, VSTRING *out) +{ + char *myname = "dict_pgsql_expand_filter"; + char *sub, + *end; + + /* + * Yes, replace all instances of %s with the address to look up. Replace + * %u with the user portion, and %d with the domain portion. + */ + sub = filter; + end = sub + strlen(filter); + while (sub < end) { + + /* + * Make sure it's %[sud] and not something else. For backward + * compatibilty, treat anything other than %u or %d as %s, with a + * warning. + */ + if (*(sub) == '%') { + char *u = value; + char *p = strrchr(u, '@'); + + switch (*(sub + 1)) { + case 'd': + if (p) + vstring_strcat(out, p + 1); + break; + case 'u': + if (p) + vstring_strncat(out, u, p - u); + else + vstring_strcat(out, u); + break; + default: + msg_warn + ("%s: Invalid filter substitution format '%%%c'!", + myname, *(sub + 1)); + break; + case 's': + vstring_strcat(out, u); + break; + } + sub++; + } else + vstring_strncat(out, sub, 1); + sub++; + } +} + +static const char *dict_pgsql_lookup(DICT *dict, const char *name) +{ + PGSQL_RES *query_res; + DICT_PGSQL *dict_pgsql; + PLPGSQL *pldb; + static VSTRING *result; + static VSTRING *query = 0; + int i, + j, + numrows; + char *name_escaped = 0; + int isFunctionCall; + int numcols; + + dict_pgsql = (DICT_PGSQL *) dict; + pldb = dict_pgsql->pldb; + /* initialization for query */ + query = vstring_alloc(24); + vstring_strcpy(query, ""); + if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) { + msg_fatal("dict_pgsql_lookup: out of memory."); + } + /* prepare the query */ + pgsql_escape_string(name_escaped, name, (unsigned int) strlen(name)); + + /* Build SQL - either a select from table or select a function */ + + isFunctionCall = (dict_pgsql->name->select_function != NULL); + if (isFunctionCall) { + vstring_sprintf(query, "select %s('%s')", + dict_pgsql->name->select_function, + name_escaped); + } else if (dict_pgsql->name->query) { + dict_pgsql_expand_filter(dict_pgsql->name->query, name_escaped, query); + } else { + vstring_sprintf(query, "select %s from %s where %s = '%s' %s", dict_pgsql->name->select_field, + dict_pgsql->name->table, + dict_pgsql->name->where_field, + name_escaped, + dict_pgsql->name->additional_conditions); + } + + if (msg_verbose) + msg_info("dict_pgsql_lookup using sql query: %s", vstring_str(query)); + + /* free mem associated with preparing the query */ + myfree(name_escaped); + + /* do the query - set dict_errno & cleanup if there's an error */ + if ((query_res = plpgsql_query(pldb, + vstring_str(query), + dict_pgsql->name->dbname, + dict_pgsql->name->username, + dict_pgsql->name->password)) == 0) { + dict_errno = DICT_ERR_RETRY; + vstring_free(query); + return 0; + } + dict_errno = 0; + /* free the vstring query */ + vstring_free(query); + numrows = PQntuples(query_res); + if (msg_verbose) + msg_info("dict_pgsql_lookup: retrieved %d rows", numrows); + if (numrows == 0) { + PQclear(query_res); + return 0; + } + numcols = PQnfields(query_res); + + if (numcols == 1 && numrows == 1 && isFunctionCall) { + + /* + * We do the above check because PostgreSQL 7.3 will allow functions + * to return result sets + */ + if (PQgetisnull(query_res, 0, 0) == 1) { + + /* + * Functions returning a single row & column that is null are + * deemed to have not found the key. + */ + PQclear(query_res); + return 0; + } + } + if (result == 0) + result = vstring_alloc(10); + + vstring_strcpy(result, ""); + for (i = 0; i < numrows; i++) { + if (i > 0) + vstring_strcat(result, ","); + for (j = 0; j < numcols; j++) { + if (j > 0) + vstring_strcat(result, ","); + vstring_strcat(result, PQgetvalue(query_res, i, j)); + if (msg_verbose > 1) + msg_info("dict_pgsql_lookup: retrieved field: %d: %s", j, PQgetvalue(query_res, i, j)); + } + } + PQclear(query_res); + return vstring_str(result); +} + +/* + * plpgsql_query - process a PostgreSQL query. Return PGSQL_RES* on success. + * On failure, log failure and try other db instances. + * on failure of all db instances, return 0; + * close unnecessary active connections + */ + +static PGSQL_RES *plpgsql_query(PLPGSQL *PLDB, + const char *query, + char *dbname, + char *username, + char *password) +{ + int i; + HOST *host; + PGSQL_RES *res = 0; + + for (i = 0; i < PLDB->len_hosts; i++) { + /* can't deal with typing or reading PLDB->db_hosts[i] over & over */ + host = &(PLDB->db_hosts[i]); + if (msg_verbose > 1) + msg_info("dict_pgsql: trying host %s stat %d, last res %p", host->hostname, host->stat, res); + + /* answer already found */ + if (res != 0 && host->stat == STATACTIVE) { + if (msg_verbose) + msg_info("dict_pgsql: closing unnessary connection to %s", + host->hostname); + plpgsql_down_host(host); + } + /* try to connect for the first time if we don't have a result yet */ + if (res == 0 && host->stat == STATUNTRIED) { + if (msg_verbose) + msg_info("dict_pgsql: attempting to connect to host %s", + host->hostname); + plpgsql_connect_single(host, dbname, username, password); + } + + /* + * try to reconnect if we don't have an answer and the host had a + * prob in the past and it's time for it to reconnect + */ + if (res == 0 && host->stat == STATFAIL && host->ts < time((time_t *) 0)) { + if (msg_verbose) + msg_info("dict_pgsql: attempting to reconnect to host %s", + host->hostname); + plpgsql_connect_single(host, dbname, username, password); + } + + /* + * if we don't have a result and the current host is marked active, + * try the query. If the query fails, mark the host STATFAIL + */ + if (res == 0 && host->stat == STATACTIVE) { + if ((res = PQexec(host->db, query))) { + if (msg_verbose) + msg_info("dict_pgsql: successful query from host %s", host->hostname); + } else { + msg_warn("%s", PQerrorMessage(host->db)); + plpgsql_down_host(host); + } + } + } + return res; +} + +/* + * plpgsql_connect_single - + * used to reconnect to a single database when one is down or none is + * connected yet. Log all errors and set the stat field of host accordingly + */ +static void plpgsql_connect_single(HOST *host, char *dbname, char *username, char *password) +{ + char *destination = host->hostname; + char *unix_socket = 0; + char *hostname = 0; + char *service; + char *port = 0; + + /* + * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where + * both "inet:" and ":port" are optional. + */ + if (strncmp(destination, "unix:", 5) == 0) { + unix_socket = destination + 5; + } else { + if (strncmp(destination, "inet:", 5) == 0) + destination += 5; + hostname = mystrdup(destination); + if ((service = split_at(hostname, ':')) != 0) + port = service; + } + + if ((host->db = PQsetdbLogin(hostname, port, NULL, NULL, dbname, username, password))) { + if (PQstatus(host->db) == CONNECTION_OK) { + if (msg_verbose) + msg_info("dict_pgsql: successful connection to host %s", + host->hostname); + host->stat = STATACTIVE; + } else + msg_warn("%s", PQerrorMessage(host->db)); + } else { + msg_warn("Unable to connect to database"); + plpgsql_down_host(host); + } + if (hostname) + myfree(hostname); +} + +/* + * plpgsql_down_host - mark a HOST down update ts if marked down + * for the first time so that we'll know when to retry the connection + */ +static void plpgsql_down_host(HOST *host) +{ + if (host->stat != STATFAIL) { + host->ts = time((time_t *) 0) + RETRY_CONN_INTV; + host->stat = STATFAIL; + } + PQfinish(host->db); + host->db = 0; +} + +/********************************************************************** + * public interface dict_pgsql_open + * create association with database with appropriate values + * parse the map's config file + * allocate memory + **********************************************************************/ +DICT *dict_pgsql_open(const char *name, int unused_flags, int unused_dict_flags) +{ + DICT_PGSQL *dict_pgsql; + + dict_pgsql = (DICT_PGSQL *) mymalloc(sizeof(DICT_PGSQL)); + dict_pgsql->dict.lookup = dict_pgsql_lookup; + dict_pgsql->dict.close = dict_pgsql_close; + dict_pgsql->name = pgsqlname_parse(name); + dict_pgsql->pldb = plpgsql_init(dict_pgsql->name->hostnames, + dict_pgsql->name->len_hosts); + if (dict_pgsql->pldb == NULL) + msg_fatal("couldn't intialize pldb!\n"); + dict_register(name, (DICT *) dict_pgsql); + return &dict_pgsql->dict; +} + +/* pgsqlname_parse - parse pgsql configuration file */ +static PGSQL_NAME *pgsqlname_parse(const char *pgsqlcf_path) +{ + int i; + char *nameval; + char *hosts; + PGSQL_NAME *name = (PGSQL_NAME *) mymalloc(sizeof(PGSQL_NAME)); + ARGV *hosts_argv; + VSTRING *opt_dict_name; + + /* + * setup a dict containing info in the pgsql cf file. the dict has a + * name, and a path. The name must be distinct from the path, or the + * dict interface gets confused. The name must be distinct for two + * different paths, or the configuration info will cache across different + * pgsql maps, which can be confusing. + */ + opt_dict_name = vstring_alloc(64); + vstring_sprintf(opt_dict_name, "pgsql opt dict %s", pgsqlcf_path); + dict_load_file(vstring_str(opt_dict_name), pgsqlcf_path); + /* pgsql username lookup */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "user")) == NULL) + name->username = mystrdup(""); + else + name->username = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set username to '%s'", name->username); + /* password lookup */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "password")) == NULL) + name->password = mystrdup(""); + else + name->password = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set password to '%s'", name->password); + + /* database name lookup */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "dbname")) == NULL) + msg_fatal("%s: pgsql options file does not include database name", pgsqlcf_path); + else + name->dbname = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set database name to '%s'", name->dbname); + + /* table lookup */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "table")) == NULL) + msg_fatal("%s: pgsql options file does not include table name", pgsqlcf_path); + else + name->table = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set table name to '%s'", name->table); + + name->select_function = NULL; + name->query = NULL; + + /* + * See what kind of lookup we have - a traditional 'select' or a function + * call + */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "select_function")) != NULL) { + + /* We have a 'select %s(%s)' function call. */ + name->select_function = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set function name to '%s'", name->table); + /* query string */ + } else if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "query")) != NULL) { + name->query = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set query to '%s'", name->query); + } else { + + /* + * We have an old style 'select %s from %s...' call, so get the + * fields + */ + + /* table lookup */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "table")) == NULL) + msg_fatal("%s: pgsql options file does not include table name", pgsqlcf_path); + else + name->table = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set table name to '%s'", name->table); + + /* select field lookup */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "select_field")) == NULL) + msg_fatal("%s: pgsql options file does not include select field", pgsqlcf_path); + else + name->select_field = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set select_field to '%s'", name->select_field); + + /* where field lookup */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "where_field")) == NULL) + msg_fatal("%s: pgsql options file does not include where field", pgsqlcf_path); + else + name->where_field = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set where_field to '%s'", name->where_field); + + /* additional conditions */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "additional_conditions")) == NULL) + name->additional_conditions = mystrdup(""); + else + name->additional_conditions = mystrdup(nameval); + if (msg_verbose) + msg_info("pgsqlname_parse(): set additional_conditions to '%s'", name->additional_conditions); + } + + /* pgsql server hosts */ + if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "hosts")) == NULL) + hosts = mystrdup(""); + else + hosts = mystrdup(nameval); + /* coo argv interface */ + hosts_argv = argv_split(hosts, " ,\t\r\n"); + + if (hosts_argv->argc == 0) { /* no hosts specified, + * default to 'localhost' */ + if (msg_verbose) + msg_info("pgsqlname_parse(): no hostnames specified, defaulting to 'localhost'"); + argv_add(hosts_argv, "localhost", ARGV_END); + argv_terminate(hosts_argv); + } + name->len_hosts = hosts_argv->argc; + name->hostnames = (char **) mymalloc((sizeof(char *)) * name->len_hosts); + i = 0; + for (i = 0; hosts_argv->argv[i] != NULL; i++) { + name->hostnames[i] = mystrdup(hosts_argv->argv[i]); + if (msg_verbose) + msg_info("pgsqlname_parse(): adding host '%s' to list of pgsql server hosts", + name->hostnames[i]); + } + myfree(hosts); + vstring_free(opt_dict_name); + argv_free(hosts_argv); + return name; +} + + +/* + * plpgsql_init - initalize a PGSQL database. + * Return NULL on failure, or a PLPGSQL * on success. + */ +static PLPGSQL *plpgsql_init(char *hostnames[], int len_hosts) +{ + PLPGSQL *PLDB; + int i; + + if ((PLDB = (PLPGSQL *) mymalloc(sizeof(PLPGSQL))) == NULL) { + msg_fatal("mymalloc of pldb failed"); + } + PLDB->len_hosts = len_hosts; + if ((PLDB->db_hosts = (HOST *) mymalloc(sizeof(HOST) * len_hosts)) == NULL) + return NULL; + for (i = 0; i < len_hosts; i++) { + PLDB->db_hosts[i] = host_init(hostnames[i]); + } + return PLDB; +} + + +/* host_init - initialize HOST structure */ +static HOST host_init(char *hostname) +{ + HOST host; + + host.stat = STATUNTRIED; + host.hostname = mystrdup(hostname); + host.db = 0; + host.ts = 0; + return host; +} + +/********************************************************************** + * public interface dict_pgsql_close + * unregister, disassociate from database, freeing appropriate memory + **********************************************************************/ +static void dict_pgsql_close(DICT *dict) +{ + int i; + DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict; + + plpgsql_dealloc(dict_pgsql->pldb); + myfree(dict_pgsql->name->username); + myfree(dict_pgsql->name->password); + myfree(dict_pgsql->name->dbname); + myfree(dict_pgsql->name->table); + myfree(dict_pgsql->name->select_field); + myfree(dict_pgsql->name->where_field); + myfree(dict_pgsql->name->additional_conditions); + for (i = 0; i < dict_pgsql->name->len_hosts; i++) { + myfree(dict_pgsql->name->hostnames[i]); + } + myfree((char *) dict_pgsql->name->hostnames); + myfree((char *) dict_pgsql->name); +} + +/* plpgsql_dealloc - free memory associated with PLPGSQL close databases */ +static void plpgsql_dealloc(PLPGSQL *PLDB) +{ + int i; + + for (i = 0; i < PLDB->len_hosts; i++) { + if (PLDB->db_hosts[i].db) + PQfinish(PLDB->db_hosts[i].db); + myfree(PLDB->db_hosts[i].hostname); + } + myfree((char *) PLDB->db_hosts); + myfree((char *) (PLDB)); +} + +#endif diff --git a/postfix/src/util/dict_pgsql.h b/postfix/src/util/dict_pgsql.h new file mode 100644 index 000000000..f597a2782 --- /dev/null +++ b/postfix/src/util/dict_pgsql.h @@ -0,0 +1,41 @@ +#ifndef _DICT_PGSQL_INCLUDED_ +#define _DICT_PGSQL_INCLUDED_ + +/*++ +/* NAME +/* dict_pgsql 3h +/* SUMMARY +/* dictionary manager interface to Postgresql files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_PGSQL "pgsql" + +extern DICT *dict_pgsql_open(const char *name, int unused_flags, int dict_flags); + +/* AUTHOR(S) +/* Aaron Sethman +/* androsyn@ratbox.org +/* +/* Based upon dict_mysql.c by +/* +/* Scott Cotton +/* IC Group, Inc. +/* scott@icgroup.com +/* +/* Joshua Marcus +/* IC Group, Inc. +/* josh@icgroup.com +/*--*/ + +#endif diff --git a/postfix/src/util/match_ops.c b/postfix/src/util/match_ops.c index 1bde84abd..d1586f7be 100644 --- a/postfix/src/util/match_ops.c +++ b/postfix/src/util/match_ops.c @@ -207,6 +207,7 @@ int match_hostaddr(int unused_flags, const char *addr, const char *pattern) unsigned long mask_bits; unsigned long net_bits; unsigned long addr_bits; + struct in_addr net_addr; if (msg_verbose) msg_info("%s: %s ~? %s", myname, addr, pattern); @@ -242,7 +243,14 @@ int match_hostaddr(int unused_flags, const char *addr, const char *pattern) if (addr_bits == INADDR_NONE) msg_fatal("%s: bad address argument: %s", myname, addr); mask_bits = htonl((0xffffffff) << (BITS_PER_ADDR - mask_shift)); - return ((addr_bits & mask_bits) == (net_bits & mask_bits)); + if ((addr_bits & mask_bits) == net_bits) + return (1); + if (net_bits & ~mask_bits) { + net_addr.s_addr = (net_bits & mask_bits); + msg_fatal("net/mask pattern %s has a non-null host portion; " + "specify %s/%d if this is really what you want", + pattern, inet_ntoa(net_addr), mask_shift); + } } return (0); }