]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.0.8-20030414
authorWietse Venema <wietse@porcupine.org>
Mon, 14 Apr 2003 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:28:48 +0000 (06:28 +0000)
31 files changed:
postfix/.indent.pro
postfix/CA-2003-12 [new file with mode: 0644]
postfix/HISTORY
postfix/INSTALL
postfix/README_FILES/PGSQL_README [new file with mode: 0644]
postfix/RELEASE_NOTES
postfix/US_PATENT_6321267 [new file with mode: 0644]
postfix/conf/postfix-files
postfix/conf/sample-misc.cf
postfix/conf/sample-pgsql-aliases.cf [new file with mode: 0644]
postfix/html/basic.html
postfix/html/virtual.5.html
postfix/man/man1/smtp-sink.1
postfix/man/man5/virtual.5
postfix/postfix-install
postfix/proto/virtual
postfix/src/global/mail_stream.c
postfix/src/global/mail_version.h
postfix/src/global/rewrite_clnt.c
postfix/src/global/smtp_stream.c
postfix/src/global/smtp_stream.h
postfix/src/nqmgr/qmgr_message.c
postfix/src/qmgr/qmgr_message.c
postfix/src/smtpstone/qmqp-source.c
postfix/src/smtpstone/smtp-sink.c
postfix/src/smtpstone/smtp-source.c
postfix/src/util/Makefile.in
postfix/src/util/dict_open.c
postfix/src/util/dict_pgsql.c [new file with mode: 0644]
postfix/src/util/dict_pgsql.h [new file with mode: 0644]
postfix/src/util/match_ops.c

index 132e9b83f99610f21450d3331097e5c451047e59..de1515131c26cbcf4457e605f64e74fbd0e7fb54 100644 (file)
@@ -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 (file)
index 0000000..e406cd0
--- /dev/null
@@ -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.
index f0b02952b5c9476d5a3b4e514ede840c468e374f..8439d67f8bd9d0e16ba98677dfca68270fcfebfb 100644 (file)
@@ -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.
 
index b2361a4883442b0b89e909375aa9b68ada009d25..b6a3465e9e66db4cce998a23918c550ab517c0ad 100644 (file)
@@ -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:
-
-    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
+Parameters whose defaults can be specified in this way are:
+
+    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 (file)
index 0000000..78675ce
--- /dev/null
@@ -0,0 +1,119 @@
+PostgreSQL map type for Postfix. Currently this code is maintained
+by LaMont Jones, <lamont@hp.com>.
+
+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
+<androsyn@ratbox.org>.  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.
index 55f25ed90da5b178d43a3ce80f444b5cdaa3f73f..72b7e1905477f79333fd7114df303e78f31bd5a7 100644 (file)
@@ -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 (file)
index 0000000..4972090
--- /dev/null
@@ -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
index cb7ad74c3a4b762dbaabea13ae0b7447376c9b01..b5175df76036dd5bb6cf230cd0791979753fc510 100644 (file)
@@ -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
index 2c83b8a348ae7084e0cee97f41656f14a3d4383b..0dca7c8e912cad15428090f2d251e448ecd9b676 100644 (file)
@@ -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 (file)
index 0000000..a31cbcc
--- /dev/null
@@ -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
index abaf634544ace53dd07c43917eccdd7f33ef493a..0e4262009b4beaadf85ba73c03a1f4861a999db8 100644 (file)
@@ -355,9 +355,10 @@ top-level domain).
 <a name="mynetworks"> <h2> My own networks </h2> </a>
 
 The <b>mynetworks</b> parameter lists all networks that this machine
-somehow trusts. This information can be used by the <a href="uce.html">
-anti-UCE</a> features to recognize trusted SMTP clients that are
-allowed to relay mail through Postfix.
+somehow trusts. This information can be used by the <a
+href="uce.html#smtpd_recipient_restrictions"> anti-UCE</a> features
+to recognize trusted SMTP clients that are allowed to relay mail
+through Postfix.
 
 <p>
 
index 2f855eac83e729eaff9a306fbba52a9c92c86cd4..30011e06589a0acfa43d09334f8d28a05dfea5ec 100644 (file)
@@ -138,9 +138,9 @@ VIRTUAL(5)                                             VIRTUAL(5)
            <i>user2@virtual-alias.domain</i>   <i>address2,</i> <i>address3</i>
 
        The <i>virtual-alias.domain</i> <i>anything</i> 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. <b>Without</b> <b>this</b> <b>entry,</b> <b>mail</b> <b>is</b> <b>rejected</b>
+       <b>with</b> <b>"relay</b> <b>access</b> <b>denied",</b> <b>or</b> <b>bounces</b>  <b>with</b>  <b>"mail</b>  <b>loops</b>
+       <b>back</b> <b>to</b> <b>myself".</b>
 
        Do  not  specify virtual alias domain names in the <b>main.cf</b>
        <b>mydestination</b> or <b>relay</b><i>_</i><b>domains</b> configuration parameters.
index 3335f076faf25bc19620e7c82efabd0c77f350b5..05e522ef05b87ac0f4c26d132b6a38a03044420a 100644 (file)
@@ -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,
index 98a64d047f833c5f59742f37aa37d1c18ac10a1e..bec609898de381e6a922c8cd4282cb6d7828fa27 100644 (file)
@@ -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.
index 6bb2044b29ab0e20be709b0d43a70def50b4a586..0bb3b3523a61590be54ba6f58f4a4a0f4b761d1e 100644 (file)
 #      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."
 
index 5c559a15a87ed7e6f5d106f4693bfd3673267588..f0d904aa0f81ce5d2b2c093281fbd3ad79ffe904 100644 (file)
 # .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.
index 6e4812e03ed361938a80c7795d134401512d814f..e07e37d592b4c9cd5eb85f648e73909d4409bc74 100644 (file)
@@ -83,6 +83,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <errno.h>
+#include <utime.h>
 
 /* 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.
index 0b847dec6300393fc8c9e71eabe2db15a32f8819..4a74a4f8b791f897ae21f4d705f3951a1ef68725 100644 (file)
   * 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;
 
  /*
index 2d76065356a1633142f79eb1a376d4dcee4f0fb4..a2cf24cd25bb862efa6411ed882277a98b72e195 100644 (file)
@@ -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;
 
     /*
index 751e92aa6ae5591539f92f1957490010ef1feecf..c2ab8ed26a03fe25a882e91b3226d2e24fffe8a2 100644 (file)
@@ -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;
 /*     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);
 
     /*
index 09f53e7bc10d2b1573d329de77d9aa3cdd63472e..29efecf1e58f11c0e6fd5eeb2078cc356a1dd594 100644 (file)
@@ -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 *);
index 3be89bfcfd9b098ea1f71db22a3ed990ffa53e86..04c52243c6e8fa77a1cefda23aeea36570ec93da 100644 (file)
@@ -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);
 
     /*
index f3bc58e49a6a3d8e9168c4bebd3fcea4608529f9..eea77dd646b32718eb1af49871d06bf429fd4d08 100644 (file)
@@ -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);
 
     /*
index 66fd33854e3e8b63a3a597263fb45112fbce2983..b2beb0202792aba5a1fdd7124ae3ed3fe81eb82b 100644 (file)
@@ -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);
index ac3fac7f554b3fcb5931ff43a5367760bba13f5e..cdcd1cd053012620da82689c4f337b5c8f338d98 100644 (file)
@@ -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 <CR><LF>.<CR><LF>");
+    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);
index b5aeaddc739f13351214fa5ab82477e0f6926ac2..c575c5a34ee74d24acb4d1372d3ec8bf25f63ab2 100644 (file)
@@ -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);
     }
 
index 4167dfff0186abcdd5724531684c95e1b8bd7a5b..19c863f43f8b01e2730edc698c531ca96f1d0c34 100644 (file)
@@ -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
index 1ce24cb9b8eb3b14f2b4c087c17dd20f902798b9..0e01366f2d19a88e306186b68abe39b1ac5302cc 100644 (file)
 #include <dict_ni.h>
 #include <dict_ldap.h>
 #include <dict_mysql.h>
+#include <dict_pgsql.h>
 #include <dict_pcre.h>
 #include <dict_regexp.h>
 #include <dict_static.h>
@@ -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 (file)
index 0000000..0fc350f
--- /dev/null
@@ -0,0 +1,712 @@
+/*++
+/* NAME
+/*     dict_pgsql 3
+/* SUMMARY
+/*     dictionary manager interface to Postgresql files
+/* SYNOPSIS
+/*     #include <dict_pgsql.h>
+/*
+/*     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 '<some username>'
+/*     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 <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <time.h>
+
+#include <postgres_ext.h>
+#include <libpq-fe.h>
+
+/* 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 (file)
index 0000000..f597a27
--- /dev/null
@@ -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 <dict_pgsql.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <dict.h>
+
+ /*
+  * 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
index 1bde84abdfa463aabd0115b48c78d0448a9c4037..d1586f7be6a423c446110c4bc650b6dc4e7aa0cd 100644 (file)
@@ -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);
 }