2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-31 06:05:37 +00:00

postfix-2.0.8-20030414

This commit is contained in:
Wietse Venema
2003-04-14 00:00:00 -05:00
committed by Viktor Dukhovni
parent 1c1296a56f
commit 5a69a3ff2c
31 changed files with 1404 additions and 109 deletions

4
postfix/.indent.pro vendored
View File

@@ -1,3 +1,7 @@
-THOST
-TPLPGSQL
-TPGSQL_NAME
-TDICT_PGSQL
-TABOUNCE
-TALIAS_TOKEN
-TARGV

42
postfix/CA-2003-12 Normal file
View File

@@ -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.

View 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.

View 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:
Parameters whose defaults can be specified in this way are:
Macro name default value for
-------------------------------------
DEF_COMMAND_DIR command_directory
DEF_DAEMON_DIR daemon_directory
DEF_SENDMAIL_PATH sendmail_path
DEF_MAILQ_PATH mailq_path
DEF_NEWALIAS_PATH newaliases_path
Macro name default value for typical default
-----------------------------------------------------------
DEF_COMMAND_DIR command_directory /usr/sbin
DEF_CONFIG_DIR config_directory /etc/postfix
DEF_DAEMON_DIR daemon_directory /usr/libexec/postfix
DEF_MAILQ_PATH mailq_path /usr/bin/mailq
DEF_MANPAGE_DIR manpage_directory /usr/local/man
DEF_NEWALIAS_PATH newaliases_path /usr/bin/newaliases
DEF_README_DIR readme_directory no (do not install)
DEF_SAMPLE_DIR sample_directory /etc/postfix
DEF_SENDMAIL_PATH sendmail_path /usr/sbin/sendmail
In order to build Postfix for very large applications, where you
expect to run more than 1000 delivery processes, you may need to

View File

@@ -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.

View 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
=========================================================

124
postfix/US_PATENT_6321267 Normal file
View File

@@ -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

View 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

View 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.

View File

@@ -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

View 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>

View 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.

View 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,

View 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.

View File

@@ -81,36 +81,37 @@
# The built-in default directory name is the current directory.
# This parameter setting is not recorded in the installed main.cf file.
# .IP config_directory
# The destination directory for Postfix configuration files.
# The final destination directory for Postfix configuration files.
# The built-in default directory name is /etc/postfix.
# This parameter setting is not recorded in the installed main.cf file.
# This parameter setting is not recorded in the installed main.cf file
# and can be changed only by recompiling Postfix.
# .IP daemon_directory
# The destination directory for Postfix daemon programs. This directory
# should not be in the command search path of any users.
# The final destination directory for Postfix daemon programs. This
# directory should not be in the command search path of any users.
# The built-in default directory name is /usr/libexec/postfix.
# This parameter setting is recorded in the installed main.cf file.
# .IP command_directory
# The destination directory for Postfix administrative commands. This
# directory should be in the command search path of adminstrative users.
# The built-in default directory name is system dependent.
# The final destination directory for Postfix administrative commands.
# This directory should be in the command search path of adminstrative
# users. The built-in default directory name is system dependent.
# This parameter setting is recorded in the installed main.cf file.
# .IP queue_directory
# The destination directory for Postfix queues.
# The final destination directory for Postfix queues.
# The built-in default directory name is /var/spool/postfix.
# This parameter setting is recorded in the installed main.cf file.
# .IP sendmail_path
# The full destination pathname for the Postfix sendmail command.
# The final destination pathname for the Postfix sendmail command.
# This is the Sendmail-compatible mail posting interface.
# The built-in default pathname is system dependent.
# This parameter setting is recorded in the installed main.cf file.
# .IP newaliases_path
# The full destination pathname for the Postfix newaliases command.
# The final destination pathname for the Postfix newaliases command.
# This is the Sendmail-compatible command to build alias databases
# for the Postfix local delivery agent.
# The built-in default pathname is system dependent.
# This parameter setting is recorded in the installed main.cf file.
# .IP mailq_path
# The full destination pathname for the Postfix mailq command.
# The final destination pathname for the Postfix mailq command.
# This is the Sendmail-compatible command to list the mail queue.
# The built-in default pathname is system dependent.
# This parameter setting is recorded in the installed main.cf file.
@@ -286,30 +287,30 @@ distribution to other machines."
tempdir_prompt="a directory for scratch files while installing
Postfix. You must have write permission in this directory."
config_directory_prompt="the destination directory for installed
Postfix configuration files."
config_directory_prompt="the final destination directory for
installed Postfix configuration files."
daemon_directory_prompt="the destination directory for installed
Postfix daemon programs. This directory should not be in the
command search path of any users."
daemon_directory_prompt="the final destination directory for
installed Postfix daemon programs. This directory should not be
in the command search path of any users."
command_directory_prompt="the destination directory for installed
Postfix administrative commands. This directory should be in the
command search path of adminstrative users."
command_directory_prompt="the final destination directory for
installed Postfix administrative commands. This directory should
be in the command search path of adminstrative users."
queue_directory_prompt="the destination directory for Postfix
queue_directory_prompt="the final destination directory for Postfix
queues."
sendmail_path_prompt="the full destination pathname for the installed
Postfix sendmail command. This is the Sendmail-compatible mail
posting interface."
sendmail_path_prompt="the final destination pathname for the
installed Postfix sendmail command. This is the Sendmail-compatible
mail posting interface."
newaliases_path_prompt="the full destination pathname for the
newaliases_path_prompt="the final destination pathname for the
installed Postfix newaliases command. This is the Sendmail-compatible
command to build alias databases for the Postfix local delivery
agent."
mailq_path_prompt="the full destination pathname for the installed
mailq_path_prompt="the final destination pathname for the installed
Postfix mailq command. This is the Sendmail-compatible mail queue
listing command."

View File

@@ -129,9 +129,9 @@
# .fi
# .sp
# The \fIvirtual-alias.domain anything\fR entry is required for a
# virtual alias domain. Without this entry, mail is rejected
# virtual alias domain. \fBWithout this entry, mail is rejected
# with "relay access denied", or bounces with
# "mail loops back to myself".
# "mail loops back to myself".\fR
#
# Do not specify virtual alias domain names in the \fBmain.cf
# mydestination\fR or \fBrelay_domains\fR configuration parameters.

View 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.

View File

@@ -20,10 +20,10 @@
* Patches change the patchlevel and the release date. Snapshots change the
* release date only, unless they include the same bugfix as a patch release.
*/
#define MAIL_RELEASE_DATE "20030319"
#define MAIL_RELEASE_DATE "20030414"
#define VAR_MAIL_VERSION "mail_version"
#define DEF_MAIL_VERSION "2.0.7-" MAIL_RELEASE_DATE
#define DEF_MAIL_VERSION "2.0.8-" MAIL_RELEASE_DATE
extern char *var_mail_version;
/*

View 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;
/*

View 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;
@@ -53,9 +56,11 @@
/* The stream is configured to enable exception handling.
/* .PP
/* smtp_printf() formats its arguments and writes the result to
/* the named stream, followed by a CR LF pair. The stream is flushed.
/* the named stream, followed by a CR LF pair. The stream is NOT flushed.
/* Long lines of text are not broken.
/*
/* smtp_flush() flushes the named stream.
/*
/* smtp_get() reads the named stream up to and including
/* the next LF character and strips the trailing CR LF. The
/* \fImaxlen\fR argument limits the length of a line of text,
@@ -152,6 +157,29 @@ void smtp_timeout_setup(VSTREAM *stream, int maxtime)
VSTREAM_CTL_END);
}
/* smtp_flush - flush stream */
void smtp_flush(VSTREAM *stream)
{
int err;
/*
* Do the I/O, protected against timeout.
*/
smtp_timeout_reset(stream);
err = vstream_fflush(stream);
smtp_timeout_detect(stream);
/*
* See if there was a problem.
*/
if (err != 0) {
if (msg_verbose)
msg_info("smtp_flush: EOF");
vstream_longjmp(stream, SMTP_ERR_EOF);
}
}
/* smtp_vprintf - write one line to SMTP peer */
void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap)
@@ -164,7 +192,7 @@ void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap)
smtp_timeout_reset(stream);
vstream_vfprintf(stream, fmt, ap);
vstream_fputs("\r\n", stream);
err = vstream_fflush(stream);
err = vstream_ferror(stream);
smtp_timeout_detect(stream);
/*

View 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 *);

View 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);
/*

View 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);
/*

View 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);

View 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);

View 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);
}

View 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

View File

@@ -174,6 +174,7 @@
#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

View File

@@ -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

View File

@@ -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

View 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);
}