+-THOST
+-TPLPGSQL
+-TPGSQL_NAME
+-TDICT_PGSQL
-TABOUNCE
-TALIAS_TOKEN
-TARGV
--- /dev/null
+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.
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.
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
--- /dev/null
+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.
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
=========================================================
--- /dev/null
+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
$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
# 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.
--- /dev/null
+#
+# 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
<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>
<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.
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,
.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.
# 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.
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."
# .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.
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
+#include <utime.h>
/* Utility library. */
{
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
* 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()
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.
* 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;
/*
VSTRING *rewrite_clnt(const char *rule, const char *addr, VSTRING *result)
{
- char *myname = "rewrite_clnt";
VSTREAM *stream;
/*
/* 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,
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)
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);
/*
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 *);
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,
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);
/*
"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,
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);
/*
static void start_connect(SESSION *session)
{
int fd;
+ struct linger linger;
/*
* Some systems don't set the socket error when connect() fails early
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);
/* 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,
if (!disable_8bitmime)
smtp_printf(state->stream, "250-8BITMIME");
smtp_printf(state->stream, "250 ");
+ smtp_flush(state->stream);
}
/* helo_response - respond to HELO command */
static void helo_response(SINK_STATE *state)
{
smtp_printf(state->stream, "250 %s", var_myhostname);
+ smtp_flush(state->stream);
}
/* ok_response - send 250 OK */
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 */
{
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;
}
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);
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)
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. */
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);
}
}
break;
case 'P':
pretend_pix = 1;
+ disable_esmtp = 1;
break;
case 's':
openlog(basename(argv[0]), LOG_PID, LOG_MAIL);
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 */
static void start_connect(SESSION *session)
{
int fd;
+ struct linger linger;
/*
* Some systems don't set the socket error when connect() fails early
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);
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);
}
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 \
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
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
#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>
#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
--- /dev/null
+/*++
+/* 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
--- /dev/null
+#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
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);
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);
}