2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-30 13:48:06 +00:00

postfix-2.0.16-20040104

This commit is contained in:
Wietse Venema
2004-01-04 00:00:00 -05:00
committed by Viktor Dukhovni
parent 8f5ee7e4ad
commit 8fb3e35efe
97 changed files with 2860 additions and 2335 deletions

1
postfix/.indent.pro vendored
View File

@@ -14,6 +14,7 @@
-TBOUNCE_INFO
-TBOUNCE_LOG
-TBOUNCE_STAT
-TCFG_PARSER
-TCLEANUP_STATE
-TCLIENT_LIST
-TCLNT_STREAM

View File

@@ -8958,6 +8958,46 @@ Apologies for any names omitted.
like reject_unlisted_recipient, but will eventually be
removed from Postfix.
20040102
Misc documentation cleanup by Loic Minier.
20040104
Workaround: MacOSX dumps core on the 20030913 TZ censoring
code. We explictly set TZ=UTC, which will produce incorrect
results when "mailq" formatting is moved from the showq
daemon to the postqueue command. File: msg_syslog.c.
Feature: after mail is requeued with "postsuper -r", the
pickup server logs the old queue ID together with the new
queue ID. Victor Duchovni. File: pickup/pickup.c.
Feature: smtpd_sasl_application_name parameter (default:
smtpd) to control the name of the SASL configuration file
used by the Postfix SMTP server. Liviu Daia. Files:
mail_params.h, smtpd.c, smtpd_sasl_glue.c.
Cleanup: shared config file parser for ldap, mysql, pgsql
lookup tables. Liviu Daia. Files: global/cfgparser.[hc],
global/dict_ldap.c, global/dict_mtsql.c, global/dict_pgsql.c
and documentation.
Cleanup: moved modules with dependencies on higher-level
code from the util directory to the global directory:
util/dict_open.c, global/cfgparser.[hc], global/dict_ldap.c,
global/dict_mtsql.c, global/dict_pgsql.c, global/mail_dict.c.
Cleanup: the new queue manager nqmgr replaces the default
queue manager qmgr, leaving behind a hard link for backwards
compatibility. The old queue manager remains available as
as oqmgr but will eventually be removed.
Bugfix: vstring_get() etc. now return VSTREAM_EOF when they
terminate prematurely, instead of returning the last
character stored. This avoids mis-leading warnings. File:
global/vstring_vstream.c.
Open problems:
Low: in the SMTP client, pass the session, request and

View File

@@ -67,11 +67,13 @@ At some point in time, a version of Postfix was supported on:
Linux Debian 1.3.1
Linux Debian 2.x
Linux Debian 3.x
Linux RedHat 3.x (August 2002)
Linux RedHat 3.x (Januway 2004, compile-only test)
Linux RedHat 4.x
Linux RedHat 5.x
Linux RedHat 6.x
Linux RedHat 7.x
Linux RedHat 8.x
Linux RedHat 9.x
Linux Slackware 3.x
Linux Slackware 4.x
Linux Slackware 7.x
@@ -89,8 +91,8 @@ At some point in time, a version of Postfix was supported on:
OpenBSD 3.x
Reliant UNIX 5.x
Rhapsody 5.x
SunOS 4.1.x (December 2002)
SunOS 5.4..5.8 (Solaris 2.4..8)
SunOS 4.1.4 (January 2004, running as MTA)
SunOS 5.4..5.9 (Solaris 2.4..9)
Ultrix 4.x (well, that was long ago)
or something closely resemblant.

View File

@@ -3,10 +3,10 @@ WARN = -Wmissing-prototypes -Wformat
OPTS = 'CC=$(CC)'
DIRS = src/util src/global src/dns src/master src/postfix src/smtpstone \
src/sendmail src/error src/pickup src/cleanup src/smtpd src/local \
src/lmtp src/trivial-rewrite src/qmgr src/smtp src/bounce src/pipe \
src/showq src/postalias src/postcat src/postconf src/postdrop \
src/lmtp src/trivial-rewrite src/qmgr src/oqmgr src/smtp src/bounce \
src/pipe src/showq src/postalias src/postcat src/postconf src/postdrop \
src/postkick src/postlock src/postlog src/postmap src/postqueue \
src/postsuper src/nqmgr src/qmqpd src/spawn src/flush src/verify \
src/postsuper src/qmqpd src/spawn src/flush src/verify \
src/virtual src/proxymap src/anvil
MANDIRS = proto man html

View File

@@ -1,3 +1,10 @@
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
===============================================================
The sender/recipient address verification feature described in this
document is suitable only for low-traffic sites. It performs poorly
under high load. See the "Limitations" section below for details.
Address verification
====================

View File

@@ -6,9 +6,9 @@ then delivers it. With the external content filter described here,
mail is filtered AFTER it is queued. This gives you maximal control
over how many filtering processes you are willing to run in parallel.
[This is not to be confused with the approach that is described in
This is not to be confused with the approach that is described in
the SMTPD_PROXY_README document, where SMTP mail is filtered BEFORE
it is queued]
it is queued.
An external content filter receives unfiltered mail from Postfix
and does one of the following:
@@ -171,7 +171,8 @@ runs into a resource problem. This approach uses content filtering
software that can receive and deliver mail via SMTP.
Some Anti-virus software is built to receive and deliver mail via
SMTP and is ready to use as an advanced external content filter.
SMTP and is ready to use as an external content filter.
For non-SMTP capable content filtering software, Bennett Todd's
SMTP proxy implements a nice PERL/SMTP content filtering framework.
See: http://bent.latency.net/smtpprox/
@@ -209,7 +210,7 @@ Postfix via localhost port 10026.
To enable content filtering in this manner, specify in main.cf:
/etc/postfix/main.cf:
/etc/postfix/main.cf:
content_filter = scan:localhost:10025
receive_override_options = no_address_mappings
@@ -223,39 +224,43 @@ so that the content filter sees the original mail addresses instead
of the result of virtual alias expansion, canonical mapping, address
masquerading, etc.
When a queue file has content filtering information, the queue
manager will deliver the mail to the specified content filter
regardless of its final destination.
Content filtering information is stored in queue files; this is
how Postfix keeps track of what mail needs filtering. When a queue
file contains content filter information, the queue manager will
deliver the mail to the specified content filter regardless of its
final destination.
In this example, "scan" is an instance of the Postfix SMTP client
with slightly different configuration parameters. This is how
one would set up the service in the Postfix master.cf file:
/etc/postfix/master.cf:
/etc/postfix/master.cf:
# =============================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# =============================================================
scan unix - - n - 10 smtp
-o myhostname=localhost.my.domain
# -o smtp_send_xclient_command=yes
The "-o myhostname=localhost.domain.tld" overrides main.cf and
avoids false alarms ("host <servername> greeted me with my own
hostname") that result in mail being bounced.
-o smtp_send_xforward_command=yes
Instead of a limit of 10 concurrent processes, use whatever process
limit is feasible for your machine. Content inspection software
can gobble up a lot of system resources, so you don't want to have
too much of it running at the same time.
Uncomment (but keep the leading white-space) the option setting:
"smtp_send_xclient_command=yes", if you would like the scan transport
to forward the original client name and IP address to the after-filter
smtpd so that filtered mail is logged with the real client name IP
address. See sample-smtp.cf and smtp(8).
With "-o smtp_send_xforward_command=yes", the scan transport will
try to forward the original client name and IP address to the
after-filter smtpd so that filtered mail is logged with the real
client name IP address. See sample-smtp.cf and smtp(8).
The content filter can be set up with the Postfix spawn service,
which is the Postfix equivalent of inetd. For example, to instantiate
up to 10 content filtering processes on demand:
/etc/postfix/master.cf:
/etc/postfix/master.cf:
# ===================================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# ===================================================================
localhost:10025 inet n n n - 10 spawn
user=filter argv=/some/where/filter localhost 10026
@@ -269,12 +274,6 @@ you want to have your filter listening on port localhost:10025
instead of Postfix, then you must run your filter as a stand-alone
program.
Note: the localhost port 10025 SMTP server filter should announce
itself as "220 localhost...". Postfix aborts delivery when it
connects to an SMTP server that uses the same hostname as Postfix
("host <servername> greeted me with my own hostname"), because that
normally means you have a mail delivery loop problem.
The example here assumes that the /some/where/filter command is a
PERL script. PERL has modules that make talking SMTP easy. The
command-line specifies that mail should be sent back into Postfix
@@ -290,7 +289,11 @@ The job of the content filter is to either bounce mail with a
suitable diagnostic, or to feed the mail back into Postfix through
a dedicated listener on port localhost 10026:
/etc/postfix/master.cf:
/etc/postfix/master.cf:
# ===================================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# ===================================================================
localhost:10026 inet n - n - 10 smtpd
-o content_filter=
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
@@ -299,6 +302,7 @@ a dedicated listener on port localhost 10026:
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
Note: do not use spaces around the "=" or "," characters.
@@ -309,12 +313,18 @@ The "-o content_filter=" overrides main.cf and requests no content
filtering for incoming mail. This is required or else mail will
stay in the content filtering loop.
The "-o receive_override_options" line overrides main.cf and turns
off table lookups that were already done before the content filter:
attempts to find out if a recipient is unknown, and header/body
checks that can suck up lots of CPU cycles. These override options
are either implemented by the SMTP server itself, or they are passed
on to the cleanup server.
The "-o receive_override_options" overrides main.cf. It is
complementary to the the options that are specified in main.cf:
- Disable attempts to find out if a recipient is unknown, and
disable header/body checks. This work was already done before
the content filter and repeating it would be wasteful.
- Enable virtual alias expansion, canonical mappings, address
masquerading, and other address mappings.
These receive override options are either implemented by the SMTP
server itself, or they are passed on to the cleanup server.
The "-o smtpd_xxx_restrictions" and "-o mynetworks=127.0.0.0/8"
override main.cf and turn off UCE controls that would only waste
@@ -367,14 +377,12 @@ and cleanup daemons or else you will have a content filtering loop.
Limitations:
- There can be only one content filter action per message.
- FILTER actions from smtpd access maps and header/body_checks take
precedence over filters specified with the main.cf content_filter
parameter.
- Only the last FILTER action from smtpd access maps or from
header/body_checks takes effect.
- If a message triggers more than one filter action, only the last
one takes effect.
- The same content filter is applied to all the recipients of a
given message.

View File

@@ -67,6 +67,28 @@ hosts = host1.some.domain host2.some.domain unix:/file/name
# end mysql config file
Alternatively, these parameters can be defined in main.cf. To that
effect, the map should be specified as "mysql:name", and the parameters
should be prefixed with the name you've given the map in its definition,
and an underscore. For example, the above settings can also be written
in main.cf as:
# mysql definitions in main.cf
alias_maps = mysql:mysqlsource
mysqlsource_user = someone
mysqlsource_password = some_password
mysqlsource_dbname = customer_database
mysqlsource_table = mxaliases
mysqlsource_select_field = forw_addr
mysqlsource_where_field = alias
mysqlsource_additional_conditions = and status = 'paid'
mysqlsource_hosts = host1.some.domain host2.some.domain unix:/file/name
# end mysql definitions
Some notes:
This configuration interface setup allows for multiple mysql

View File

@@ -27,9 +27,8 @@ Postfix Installation parameters
Postfix installation is controlled by a dozen installation parameters.
See the postfix-install and post-install files for details. Most
parameters have system-dependent default settings that aren't
configurable at compile time. This will hopefully be rectified in
a later release.
parameters have system-dependent default settings that are configurable
at compile time, as described in the INSTALL file.
Preparing a pre-built package for distribution to other systems
===============================================================
@@ -38,10 +37,9 @@ You can build a Postfix package on a machine that does not have
Postfix installed on it. All you need is Postfix source code and
a compilation environment that is compatible with the target system.
You can build a pre-built Postfix package as an unprivileged user,
but the result will be cleaner if you build the package as root.
You can build a pre-built Postfix package as an unprivileged user.
After successful Postfix compilation, execute:
First compile Postfix. After successful compilation, execute:
% sh postfix-install
@@ -73,7 +71,7 @@ Thus, to tar up the pre-built package, take the following steps:
% cd INSTALL_ROOT
% rm -f SOMEWHERE/outputfile
% find . \! -type d -print | xargs tar rf SOMEWHERE/outputfile
% find . \! -type d -print | xargs tar cf SOMEWHERE/outputfile
% gzip SOMEWHERE/outputfile
This way you will not include any directories that might cause trouble

View File

@@ -80,15 +80,28 @@ additional_conditions = 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).
# Alternatively, you can override the default SELECT statement (and the
# above table, select_field, where_field, and additional_conditions) by
# specifying the query:
#query = select forw_addr from mxaliases where alias = '%s' and status = 'paid'
# Before the query is actually issued, all occurences of %s are replaced
# with the address to look up, %u are replaced with the user portion,
# and %d with the domain portion.
# 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:
# 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.
# This is equivalent to:
#
#query = select my_lookup_user_alias('%s')
#
# and overrides both the query parameter and the table-related fields
# above.
#
# 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
@@ -105,6 +118,29 @@ hosts = host1.some.domain host2.some.domain unix:/file/name
# end pgsql config file
Alternatively, these parameters can be defined in main.cf. To that
effect, the map should be specified as "pgsql:name", and the parameters
should be prefixed with the name you've given the map in its definition,
and an underscore. For example, the above settings can also be written
in main.cf as:
# pgsql definitions in main.cf
alias_maps = pgsql:pgsqlsource
pgsqlsource_user = someone
pgsqlsource_password = some_password
pgsqlsource_dbname = customer_database
pgsqlsource_table = mxaliases
pgsqlsource_select_field = forw_addr
pgsqlsource_where_field = alias
pgsqlsource_additional_conditions = and status = 'paid'
#pgsqlsource_select_function = my_lookup_user_alias
pgsqlsource_hosts = host1.some.domain host2.some.domain unix:/file/name
# end mysql definitions
Using mirrored databases
========================

View File

@@ -2,12 +2,14 @@ What this file is about
=======================
This is the beginning of documentation for the clever queue manager
scheduling algorithm by Patrik Rak. For too much time, this code
has been made available in the nqmgr queue manager as an optional
module.
scheduling algorithm by Patrik Rak. For a long time, this code was
made available under the name "nqmgr" (new queue manager), as an
optional module. It now become the default queue manager, which is
always called "qmgr". The old queue manager will for some time will
be available under the name of "oqmgr".
Why Postfix ships two queue managers
====================================
Why the old Postfix queue manager was replaced
==============================================
The old Postfix scheduler had several limitations due to unfortunate
choices in its design.
@@ -31,15 +33,6 @@ choices in its design.
large delays. Patrik Rak's scheduler allows mail with fewer
recipients to slip past bulk mail in an elegant manner.
If this newer scheduler is so much better, why does Postfix still
ship the old one? The problem is that there isn't a whole lot of
documentation on how Patrik's code works, so that no-one except
Patrik understands how it works.
This document is the start of something that will help to clarify
things. Once enough documentation exists we can end the embarassing
situation of shipping two queue managers.
How the queue manager scheduler works
=====================================
@@ -92,7 +85,7 @@ into core in batches and which is triggered at appropriate moments.
4) Doing things efficiently: All important things I am aware of
are done in the minimum time possible (either directly or at least
when ammortized complexity is used), but to choose which job is
when amortized complexity is used), but to choose which job is
the best candidate for preempting the current job requires linear
search of up to all transport jobs (the worst theoretical case -
the reality is much better). As this is done every time the next

View File

@@ -47,9 +47,9 @@ server sends in a delegated SMTPD access policy request:
The following is specific to SMTPD delegated policy requests:
- Protocol names are ESMTP or SMTP.
- Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT, DATA or
ETRN; these are the SMTP protocol states where the Postfix SMTP
server makes an OK/REJECT/HOLD/etc. decision.
- Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT, DATA, VRFY
or ETRN; these are the SMTP protocol states where the Postfix
SMTP server makes an OK/REJECT/HOLD/etc. decision.
The policy server replies with any action that is allowed in a
Postfix SMTPD access table. Example:
@@ -57,8 +57,11 @@ Postfix SMTPD access table. Example:
action=450 Service temporarily unavailable
[empty line]
In case of trouble the server must log a warning and disconnect.
Postfix will retry the request at some later time.
This causes Postfix to reject the request with a 450 temporary
error code and with text "Service temporarily unavailable".
In case of trouble the policy server must log a warning and
disconnect. Postfix will retry the request at some later time.
CLIENT SIDE CONFIGURATION
=========================
@@ -93,9 +96,10 @@ or else your system could become an open relay.
NOTE: Postfix by default kills a command after 1000 seconds. This
is too short for a policy daemon that may run for as long as an
SMTP client is connected to an SMTP server process.
SMTP client is connected to an SMTP server process. The default
time limit is overruled with an explicit policy_time_limit setting.
NOTE: Solaris UNIX-domain sockets do not work very well. Use TCP
NOTE: Solaris UNIX-domain sockets do not work reliably. Use TCP
sockets instead:
/etc/postfix/main.cf:
@@ -109,15 +113,17 @@ sockets instead:
Other client-side configuration parmeters:
- smtpd_policy_service_max_idle (default: 300s): the amount of time
before Postfix closes an unused policy connection. The socket is
closed while the SMTP server waits for new a SMTP client.
before the Postfix SMTP server closes an unused policy client
connection. The socket is closed while the SMTP server waits
for new a SMTP client.
- smtpd_policy_service_max_ttl (default: 1000s): the amount of time
before Postfix closes an active policy connection. The socket
is closed while the SMTP server waits for new a SMTP client.
before the Postfix SMTP server closes an active policy client
connection. The socket is closed while the SMTP server waits for
new a SMTP client.
- smtpd_policy_service_timeout (default: 100s): the time limit to
connect to, send to or receive from a policy service.
connect to, send to or receive from a policy server.
EXAMPLE: GREYLIST POLICY SERVER
===============================
@@ -125,11 +131,11 @@ EXAMPLE: GREYLIST POLICY SERVER
The file examples/smtpd-policy/smtpd-policy.pl in the Postfix source
tree implements an example greylist policy server. This server
stores a time stamp for every (client, sender, recipient) triple.
By default, mail is not accepted until a triple's time stamp is
more than 3600 seconds old. This stops junk mail with randomly
selected sender addresses, and mail that is sent through randomly
selected open proxies. It also stops junk mail from spammers that
change their IP address frequently.
By default, mail is not accepted until a time stamp is more than
60 seconds old. This stops junk mail with randomly selected sender
addresses, and mail that is sent through randomly selected open
proxies. It also stops junk mail from spammers that change their
IP address frequently.
Copy examples/smtpd-policy/smtpd-policy.pl to /usr/libexec/postfix
or whatever location is appropriate for your system.
@@ -171,11 +177,12 @@ Greylisting mail from frequently forged domains
It is relatively safe to turn on greylisting for specific domains
that often appear in forged email. However, sites like AOL may
blacklist you when they find that you are probing them too often
and/or if you're probing them too often for non-existent addresses.
blacklist you when they find that you are probing them too often,
or when you're probing them too often for non-existent addresses.
/etc/postfix/main.cf:
smtpd_recipient_restrictions =
reject_unlisted_recipient
...
reject_unauth_destination
check_sender_access hash:/etc/postfix/sender_access
@@ -193,6 +200,10 @@ and/or if you're probing them too often for non-existent addresses.
Be sure to specify check_sender_access AFTER reject_unauth_destination
or else your system could become an open mail relay.
The greylist database gets polluted quickly with bogus addresses.
It can help if you protect greylist lookups with restrictions that
reject unknown senders and/or recipients.
A list of frequently forged MAIL FROM domains can be found at
http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in
@@ -206,6 +217,7 @@ database relatively quickly.
/etc/postfix/main.cf:
smtpd_recipient_restrictions =
reject_unlisted_recipient
...
reject_unauth_destination
check_sender_access hash:/etc/postfix/sender_access
@@ -221,6 +233,10 @@ Be sure to specify check_sender_access and check_policy_service
AFTER reject_unauth_destination or else your system could become
an open mail relay.
The greylist database gets polluted quickly with bogus addresses.
It can help if you protect greylist lookups with restrictions that
reject unknown senders and/or recipients.
ROUTINE MAINTENANCE
===================
@@ -228,10 +244,11 @@ The greylist database grows over time, because the greylist server
never removes database entries. If left unattended, the greylist
database will eventually run your file system out of space.
When the status file exceeds some reasonable size you can simply
delete the file without adverse effects. In the worst case, new
mail will be delayed by one hour. To lessen the impact, delete the
file in the middle of the night during a weekend.
When the status file size exceeds some threshold you can simply
rename or remove the file without adverse effects. In the worst
case, new mail will be delayed by an hour or so. To lessen the
impact, rename or remove the file in the middle of the night at
the beginning of a weekend.
SAMPLE POLICY ROUTINE
=====================
@@ -245,7 +262,7 @@ This is the PERL subroutine that implements the example greylist policy.
# that can run out of space.
#
$database_name="/var/mta/smtpd-policy.db";
$greylist_delay=3600;
$greylist_delay=60;
#
# Demo SMTPD access policy routine. The result is an action just like
@@ -259,8 +276,8 @@ sub smtpd_access_policy {
open_database() unless $database_obj;
# Lookup the time stamp for this client/sender/recipient.
$key = $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
$key =~ tr /A-Z/a-z/;
$key =
lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
$time_stamp = read_database($key);
$now = time();
@@ -278,6 +295,6 @@ sub smtpd_access_policy {
if ($now - $time_stamp > $greylist_delay) {
return "dunno";
} else {
return "defer_if_permit Service is unavailable";
return "defer_if_permit Service temporarily unavailable";
}
}

View File

@@ -1,3 +1,9 @@
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
===============================================================
The content filtering approach described in this document is suitable
only for low-traffic sites. It performs poorly under high load. See
the "Pros ans Cons" section below for details.
Purpose of the before-queue content filter feature
==================================================
@@ -8,36 +14,19 @@ queue.
The before-queue content filter is meant to be used as follows:
before / smtp
Internet -> smtpd -> queue -> smtpd -> cleanup -> queue -> local
Postfix filter Postfix \ virtual etc.
Normally, Postfix receives mail, stores it in the mail queue and
then delivers it. The next diagram shows the normal path through
Postfix for mail that arrives via the network:
/ smtp
Internet -> smtpd -> cleanup -> queue -> local
Postfix \ virtual etc.
BEFORE / smtp
Internet -> smtpd -> QUEUE -> smtpd -> cleanup -> queue -> local
Postfix FILTER Postfix \ virtual etc.
The before-queue content filter is not to be confused with the
approach described in the FILTER_README document, where mail is
filtered AFTER it is stored in the Postfix mail queue. For example,
below is the FILTER_README approach with an SMTP-based after-queue
content filter:
filtered AFTER it is stored in the Postfix mail queue.
/ smtp
Internet -> smtpd -> cleanup -> queue -> local
Postfix ^ v \ virtual etc.
smtpd smtp
Postfix Postfix
\ after /
queue <-
filter
Principles of operation
=======================
The before-queue content filter functions just like the after-queue
content filter. It receives unfiltered SMTP mail from Postfix and
does one of the following:
The filter receives unfiltered SMTP mail from Postfix and does one
of the following:
1 - Re-inject the mail back into Postfix via SMTP, perhaps after
changing content.
@@ -48,8 +37,96 @@ does one of the following:
3 - Send the mail somewhere else, or discard the mail.
Limitations
===========
The before-queue content filter functions just like the after-queue
content filter. In many cases you can use the same content filtering
software, with some limitations as discussed in the "Pros and Cons"
section below.
Using the SMTP pass-through proxy feature
=========================================
In the following example, the Postfix SMTP server gives mail to a
content filter that listens on localhost port 10025, and receives
mail from the content filter via localhost port 10026. From then
on mail is processed as usual.
The result looks as follows:
Internet
-> Postfix SMTP server on port 25
-> filter on localhost port 10025
-> Postfix SMTP server on localhost port 26
-> cleanup
-> queue
This is configured by editing the master.cf file:
/etc/postfix/master.cf:
# =============================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# =============================================================
#
# Before-filter SMTP server. Receive mail from the network and
# pass it to the content filter on localhost port 10025.
#
smtp inet n - n - 20 smtpd
-o smtpd_proxy_filter=localhost:10025
#
# After-filter SMTP server. Receive mail from the content filter
# on localhost port 10026.
#
:10026 inet n - n - - smtpd
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
-o smtpd_client_restrictions=
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o receive_override_options=no_unknown_recipient_checks
Note: do not specify spaces around the "=" or "," characters.
The before-filter SMTP server entry is a modified version of the
default Postfix SMTP server entry that is normally configured at
the top of the master.cf file:
- The number of SMTP sessions is reduced from the default 100 to
only 20. This prevents a burst of mail from running your system
into the ground with too many content filter processes.
- The "-o smtpd_proxy_filter=localhost:10025" tells the before
filter SMTP server that it should give incoming mail to the
content filter that listens on localhost port 10025.
The after-filter SMTP server is a new master.cf entry:
- The ":10026" makes the after-filter SMTP server listen on the
localhost address only, without exposing it to the network.
NEVER expose the after-filter SMTP server to the Internet :-)
- The "-o smtpd_authorized_xforward_hosts=127.0.0.0/8" allows the
after-filter SMTP server to receive remote SMTP client information
from the before filter SMTP server, so that the after-filter
Postfix daemons log the remote SMTP client information instead
of logging localhost[127.0.0.1].
- The other after-filter SMTP server settings avoid duplication of
work that is already done in the "before filter" SMTP server.
The filter itself is not described here. You can use any filter
that is SMTP enabled. For non-SMTP capable content filtering
software, Bennett Todd's SMTP proxy implements a nice PERL/SMTP
content filtering framework. See: http://bent.latency.net/smtpprox/
By default, the filter has 100 seconds to do its work. If it takes
longer then Postfix gives up and reports an error to the remote
SMTP client. You can increase this time limit (see configuration
parameter section below) but doing so is pointless because you
can't control when the remote SMTP client times out.
Pros and Cons
=============
The before-queue content filter allows Postfix to reject mail before
the incoming SMTP mail transfer completes, so that Postfix does
@@ -62,13 +139,20 @@ load increases, fewer and fewer CPU cycles remain available to
answer within the deadline, and eventually you either have to stop
accepting mail or you have to stop filtering the mail.
Another problem is that content filtering software can use lots of
memory resources. In order to not run out of memory you have to
reduce the number of before-filter SMTP server processes so that
a burst of mail will not drive your system into the ground with
too many content filter processes. This, in turn, means that SMTP
clients have to wait for a long time before they receive service.
How Postfix talks to the before-queue content filter
==================================================
The before-filter Postfix SMTP server connects to the content
filter, delivers one message, and disconnects. While sending mail
into the content filter, Postfix speaks ESMTP but uses no command
pipelining. Postfix generates its own EHLO, XCLIENT (for logging
pipelining. Postfix generates its own EHLO, XFORWARD (for logging
the remote client IP address instead of localhost[127.0.0.1]), DATA
and QUIT commands, and forwards unmodified copies of all the MAIL
FROM and RCPT TO commands that the before-filter Postfix SMTP server
@@ -110,49 +194,6 @@ smtpd_proxy_ehlo (default: $myhostname)
The hostname to use when sending an EHLO command to the
before-queue content filter.
Testing the SMTP pass-through proxy feature
===========================================
The following example sets up a null content filter, that is, the
Postfix SMTP server gives the mail directly to another Postfix SMTP
server process without intervening content filter. This useful only
for testing, of course.
/etc/postfix/master.cf
smtp inet n - n - - smtpd
-o smtpd_proxy_filter=26
:26 inet n - n - - smtpd
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
-o smtpd_client_restrictions=
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o receive_override_options=no_unknown_recipient_checks
Note: do not specify spaces around the "=" or "," characters.
The ":26" causes Postfix to listen on the localhost address only.
DO NOT expose the secondary SMTP server to the Internet :-)
The smtpd_authorized_xforward_hosts parameter allows the before
filter SMTP server to forward remote SMTP client information to
the after-filter SMTP server, so that the after-filter Postfix
daemons log the remote SMTP client information instead of logging
localhost[127.0.0.1].
The other parameter settings avoid duplication of effort that is
done in the "before filter" SMTP server.
The result is as follows:
Internet -> smtpd on port 25 -> smtpd on port 26 -> cleanup -> queue
This configuration is sufficient for stress testing.
Other suggestions for test configurations: use the Postfix smtp-sink
command as the proxy, or something as basic as netcat.
Transparency
============

View File

@@ -1,6 +1,8 @@
In order to receive mail via UUCP, your system needs to have an
rmail command installed. A minimal rmail command can be found in
the "auxiliary/rmail" directory. Install the command, mode 755, in
a place that can be found by the UUCP "uuxqt" command.
the "auxiliary/rmail" directory of the Postfix source code
distribution. Install the command, mode 755, in a place that can
be found by the UUCP "uuxqt" command.
In order to send mail via UUCP, see html/faq.html.
In order to send mail via UUCP, see html/faq.html in the Postfix
source code distribution, or http://www.postfix.org/faq.html.

View File

@@ -0,0 +1,372 @@
This code was created by Andrew McNamara <andrew@connect.com.au>
and adapted to snapshot 20001121 by Xavier Beaudouin. It was merged
with mainstream Postfix for snapshot 20010128 by Wietse.
Purpose of this software
========================
You can use the virtual delivery agent for mailbox delivery of some
or all domains that are handled by a machine.
This mechanism is different from virtual alias domains. Those
are implemented by translating every recipient address into a
different address. For that, see the virtual(5) manual page.
With the virtual delivery agent, every recipient adress can have
its own mailbox. There is no translation from recipient addresses
into different addresses.
This is what Andrew McNamara wrote when he made the virtual delivery
agent available.
"This code is designed for ISP's who offer virtual mail hosting.
It looks up the user mailbox location, uid and gid via separate
maps, and the mailbox location map can specify either mailbox or
maildir delivery (controlled by trailing slash on mailbox name).
The agent does support user+foo address extensions, but does not
support aliases or .forward files (use the virtual table instead),
and therefore doesn't support file or program aliases. This choice
was made to simplify and streamline the code (it allowed me to
dispense with 70% of local's code - mostly the bits that are a
security headache) - if you need this functionality, this agent
isn't for you.
It also doesn't support writing to a common spool as root and then
chowning the mailbox to the user - I felt this functionality didn't
fit with my overall aims."
[End of Andrew McNamara's words]
The result is the most secure local delivery agent that you will
find with Postfix.
This delivery agent requires three different lookup tables in order
to define its recipients as (mailbox path, user ID, group ID). This
is because Postfix table lookups can't return multiple results.
If your virtual mailboxes are all owned by the same user/group ID,
just specify "static" maps that always return the same result. See
below for examples.
If your virtual mailboxes must be owned by different user/group
IDs, and if it is too inconvenient for you to maintain three parallel
tables, use an LDAP or MYSQL database (or generate the three parallel
tables from one common template).
Configuration parameters
========================
virtual_mailbox_base
Specifies a path that is prepended to all mailbox paths. This
is a safety measure to ensure an that out of control map doesn't
litter the filesystem with mailboxes (or worse). While it could
be set to "/", this isn't recommended.
virtual_mailbox_domains
Specifies the list of domains that should be delivered to the
$virtual_transport delivery agent (default: virtual). As of
version 2.0, Postfix is smart enough that you don't have to
list every virtual domain in a Postfix transport map.
virtual_mailbox_maps
Recipients are looked up in this map to determine the path to
their mailbox. If the returned path ends in a slash ("/"),
maildir-style delivery is carried out, otherwise the path is
assumed to specify a mailbox file. The virtual_mailbox_base
directory is unconditionally prepended to this path. If the
recipient is not found the mail is bounced.
In a lookup table, specify a left-hand side of @domain.tld to
match any user in the specified domain that does not have her
own user@domain.tld entry. While searching a lookup table, an
extended address (user+foo@domain.tld) is searched before the
bare address (user@domain.tld).
If a recipient is not found the mail is returned to the sender.
Regular expression maps are allowed. For security reasons,
regular expression substitution of $1 etc. is disallowed,
because that would open a security hole.
The mail administrator is expected to create and chown recipient
mailbox files or maildir directories ahead of time.
virtual_minimum_uid
Specifies a minimum uid that will be accepted as a return from
a virtual_uid_maps lookup. Returned values less than this will
be rejected, and the message will be deferred.
virtual_uid_maps
Recipients are looked up in this map to determine the UID (owner
privileges) to be used when writing to the target mailbox.
In a lookup table, specify a left-hand side of @domain.tld to
match any user in the specified domain that does not have a
specific user@domain.tld entry. While searching a lookup table,
an address extension (user+foo@domain.tld) is ignored.
Regular expression maps are allowed. For security reasons,
regular expression substitution of $1 etc. is disallowed,
because that would open a security hole.
Specify a static map if all mailboxes should be owned by the same
UID. For example, to specify that all mailboxes are owned by the
UID 5000, specify:
virtual_uid_maps = static:5000
virtual_gid_maps
Recipients are looked up in this map to determine the GID (group
privileges) to be used when writing to the target mailbox.
In a lookup table, specify a left-hand side of @domain.tld to
match any user in the specified domain that does not have a
specific user@domain.tld entry. While searching a lookup table,
an address extension (user+foo@domain.tld) is ignored.
Regular expression maps are allowed. For security reasons,
regular expression substitution of $1 etc. is disallowed,
because that would open a security hole.
Specify a static map if all mailboxes should be owned by the same
GID. For example, to specify that all mailboxes are owned by the
GID 5000, specify:
virtual_gid_maps = static:5000
virtual_mailbox_lock
This setting is ignored in case of maildir delivery.
Locking method to use when updating a mailbox. Defaults to
fcntl or flock depending on the system. Depending on the POP
or IMAP server you may have to specify dotlock locking, which
requires that the recipient UID or GID has write access to the
parent directory of the mailbox file.
Use the "postconf -l" command to find out what locking methods
Postfix supports on your system.
virtual_mailbox_limit
An upper limit on the size of a mailbox file or maildir file.
Example 1: using the virtual delivery agent for all local mail
==============================================================
This example does not use the Postfix local delivery agent at all.
With this configuration Postfix does no alias expansion, no .forward
file expansion, no lookups of recipients in /etc/passwd, and allows
but ignores user+foo address extensions.
Instead of "hash" specify "dbm" or "btree", depending on your system
type. The command "postconf -m" displays possible lookup table
types.
/etc/postfix/main.cf:
# Don't send mail to the local delivery agent.
mydestination =
# All domains that are listed in $virtual_mailbox_domains
# are delivered via $virtual_transport, which is the virtual
# delivery agent by default.
virtual_mailbox_domains =
$myhostname localhost.$mydomain virtual1.domain virtual2.domain
virtual_transport = virtual
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_minimum_uid = 100
virtual_uid_maps = hash:/etc/postfix/vuid
virtual_gid_maps = hash:/etc/postfix/vgid
Define a virtual delivery agent if the entry doesn't already exist:
/etc/postfix/master.cf:
virtual unix - n n - - virtual
Example recipients, one UNIX-style mailbox, one qmail-style maildir:
/etc/postfix/vmailbox:
test1@virtual1.domain test1
test2@virtual2.domain test2/
/etc/postfix/vuid:
test1@virtual1.domain 5001
test2@virtual2.domain 5002
/etc/postfix/vgid:
test1@virtual1.domain 5001
test2@virtual2.domain 5002
Execute something like the following commands for each mailbox recipient:
# touch /var/mail/vhosts/test1
# chown 5001:5001 /var/mail/vhosts/test1
Execute something like the following commands for each maildir recipient:
# mkdir /var/mail/vhosts/test2
# chown 5002:5002 /var/mail/vhosts/test2
Be sure to make the necessary entries for root@$myhostname,
postmaster@$myhostname and for any other necessary addresses.
Example 2: co-existing with the default local delivery agent
============================================================
In this example, the default Postfix local delivery agent handles
the mail for non-virtual recipients; the virtual delivery agent
handles virtual recipients, and all virtual mailboxes are owned
by user ID 5000, group ID 5000.
Instead of "hash" specify "dbm" or "btree", depending on your system
type. The command "postconf -m" displays possible lookup table
types.
/etc/postfix/main.cf:
# All domains and users delivered by the virtual local delivery agent.
virtual_transport = virtual
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_mailbox_domains = $virtual_mailbox_maps
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# All domains and users delivered by the local delivery agent.
# local_recipient_maps is used by the SMTP server to reject mail
# for unknown users.
local_transport = local
mydestination = $myhostname $localhost.$mydomain
local_recipient_maps = unix:passwd.byname $alias_maps
Define a virtual delivery agent if the entry doesn't already exist:
/etc/postfix/master.cf:
virtual unix - n n - - virtual
Example recipients, one UNIX-style mailbox, one qmail-style maildir:
/etc/postfix/vmailbox:
test1@virtual1.domain test1
test2@virtual2.domain test2/
/etc/postfix/vmaildomains:
virtual1.domain required to prevent relay access denied errors
virtual2.domain required to prevent relay access denied errors
Execute something like the following commands for each mailbox recipient:
# touch /var/mail/vhosts/test1
# chown 5000:5000 /var/mail/vhosts/test1
Execute something like the following commands for each maildir recipient:
# mkdir /var/mail/vhosts/test2
# chown 5000:5000 /var/mail/vhosts/test2
Remember that each domain is required to have a postmaster contact
address.
Example 3: hosting many virtual users
=====================================
Example 2 is fine if you host only a few virtual users. With many
users you will want to separate the information that changes often
(the user addresses) from the information that changes rarely (the
names of hosted domains).
This example is the same as above, with co-existing local and
virtual domains, but it uses a separate table for specifying the
virtual domain names.
/etc/postfix/main.cf:
# All domains and users delivered by the virtual local delivery agent.
virtual_transport = virtual
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_mailbox_domains = hash:/etc/postfix/vmaildomains
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# All domains and users delivered by the local delivery agent.
# local_recipient_maps is used by the SMTP server to reject mail
# for unknown users.
local_transport = local
mydestination = $myhostname $localhost.$mydomain
local_recipient_maps = unix:passwd.byname $alias_maps
Define a virtual delivery agent if the entry doesn't already exist:
/etc/postfix/master.cf:
virtual unix - n n - - virtual
Example recipients, one UNIX-style mailbox, one qmail-style maildir:
/etc/postfix/vmailbox:
test1@virtual1.domain test1
test2@virtual2.domain test2/
/etc/postfix/vmaildomains:
virtual1.domain required to prevent relay access denied errors
virtual2.domain required to prevent relay access denied errors
Execute something like the following commands for each mailbox recipient:
# touch /var/mail/vhosts/test1
# chown 5000:5000 /var/mail/vhosts/test1
Execute something like the following commands for each maildir recipient:
# mkdir /var/mail/vhosts/test2
# chown 5000:5000 /var/mail/vhosts/test2
Remember that each domain is required to have a postmaster contact
address.
Example 4: forwarding mail for an old account to a new address
==============================================================
In order to forward mail for a user who no longer exists, one would
set up a rule in a virtual table (please ignore the text in the
virtual configuration file about virtual domains):
/etc/postfix/main.cf:
virtual_maps = hash:/etc/postfix/virtual
/etc/postfix/virtual:
old_user@old.domain new_user@new.domain
Example 5: setting up a virtual vacation autoresponder
======================================================
In order to set up an autoreply for virtual recipients while still
delivering mail as normal, set up a rule in a virtual table (please
ignore the text in the virtual configuration file about virtual
domains):
/etc/postfix/main.cf:
virtual_maps = hash:/etc/postfix/virtual
/etc/postfix/virtual:
user@domain.tld user@domain.tld, user@autoreply.domain.tld
This delivers mail to the recipient, and sends a copy of the mail
to the address that produces automatic replies. The address can be
serviced on a different machine, or it can be serviced locally by
setting up a transport map entry that pipes all mail for the
autoreply.domain.tld into some script that sends an automatic
reply back to the sender.

View File

@@ -1,372 +1,17 @@
This code was created by Andrew McNamara <andrew@connect.com.au>
and adapted to snapshot 20001121 by Xavier Beaudouin. It was merged
with mainstream Postfix for snapshot 20010128 by Wietse.
Purpose of this software
Porpose of this document
========================
You can use the virtual delivery agent for mailbox delivery of some
or all domains that are handled by a machine.
Hosting multiple domains on one server
This mechanism is different from virtual alias domains. Those
are implemented by translating every recipient address into a
different address. For that, see the virtual(5) manual page.
With the virtual delivery agent, every recipient adress can have
its own mailbox. There is no translation from recipient addresses
into different addresses.
UNIX system accounts, domain shared with all users
==================================================
This is what Andrew McNamara wrote when he made the virtual delivery
agent available.
UNIX system accounts, domains not shared
========================================
"This code is designed for ISP's who offer virtual mail hosting.
It looks up the user mailbox location, uid and gid via separate
maps, and the mailbox location map can specify either mailbox or
maildir delivery (controlled by trailing slash on mailbox name).
The agent does support user+foo address extensions, but does not
support aliases or .forward files (use the virtual table instead),
and therefore doesn't support file or program aliases. This choice
was made to simplify and streamline the code (it allowed me to
dispense with 70% of local's code - mostly the bits that are a
security headache) - if you need this functionality, this agent
isn't for you.
It also doesn't support writing to a common spool as root and then
chowning the mailbox to the user - I felt this functionality didn't
fit with my overall aims."
[End of Andrew McNamara's words]
The result is the most secure local delivery agent that you will
find with Postfix.
This delivery agent requires three different lookup tables in order
to define its recipients as (mailbox path, user ID, group ID). This
is because Postfix table lookups can't return multiple results.
If your virtual mailboxes are all owned by the same user/group ID,
just specify "static" maps that always return the same result. See
below for examples.
If your virtual mailboxes must be owned by different user/group
IDs, and if it is too inconvenient for you to maintain three parallel
tables, use an LDAP or MYSQL database (or generate the three parallel
tables from one common template).
Configuration parameters
========================
virtual_mailbox_base
Specifies a path that is prepended to all mailbox paths. This
is a safety measure to ensure an that out of control map doesn't
litter the filesystem with mailboxes (or worse). While it could
be set to "/", this isn't recommended.
virtual_mailbox_domains
Specifies the list of domains that should be delivered to the
$virtual_transport delivery agent (default: virtual). As of
version 2.0, Postfix is smart enough that you don't have to
list every virtual domain in a Postfix transport map.
virtual_mailbox_maps
Recipients are looked up in this map to determine the path to
their mailbox. If the returned path ends in a slash ("/"),
maildir-style delivery is carried out, otherwise the path is
assumed to specify a mailbox file. The virtual_mailbox_base
directory is unconditionally prepended to this path. If the
recipient is not found the mail is bounced.
In a lookup table, specify a left-hand side of @domain.tld to
match any user in the specified domain that does not have her
own user@domain.tld entry. While searching a lookup table, an
extended address (user+foo@domain.tld) is searched before the
bare address (user@domain.tld).
If a recipient is not found the mail is returned to the sender.
Regular expression maps are allowed. For security reasons,
regular expression substitution of $1 etc. is disallowed,
because that would open a security hole.
The mail administrator is expected to create and chown recipient
mailbox files or maildir directories ahead of time.
virtual_minimum_uid
Specifies a minimum uid that will be accepted as a return from
a virtual_uid_maps lookup. Returned values less than this will
be rejected, and the message will be deferred.
virtual_uid_maps
Recipients are looked up in this map to determine the UID (owner
privileges) to be used when writing to the target mailbox.
In a lookup table, specify a left-hand side of @domain.tld to
match any user in the specified domain that does not have a
specific user@domain.tld entry. While searching a lookup table,
an address extension (user+foo@domain.tld) is ignored.
Regular expression maps are allowed. For security reasons,
regular expression substitution of $1 etc. is disallowed,
because that would open a security hole.
Specify a static map if all mailboxes should be owned by the same
UID. For example, to specify that all mailboxes are owned by the
UID 5000, specify:
virtual_uid_maps = static:5000
virtual_gid_maps
Recipients are looked up in this map to determine the GID (group
privileges) to be used when writing to the target mailbox.
In a lookup table, specify a left-hand side of @domain.tld to
match any user in the specified domain that does not have a
specific user@domain.tld entry. While searching a lookup table,
an address extension (user+foo@domain.tld) is ignored.
Regular expression maps are allowed. For security reasons,
regular expression substitution of $1 etc. is disallowed,
because that would open a security hole.
Specify a static map if all mailboxes should be owned by the same
GID. For example, to specify that all mailboxes are owned by the
GID 5000, specify:
virtual_gid_maps = static:5000
virtual_mailbox_lock
This setting is ignored in case of maildir delivery.
Locking method to use when updating a mailbox. Defaults to
fcntl or flock depending on the system. Depending on the POP
or IMAP server you may have to specify dotlock locking, which
requires that the recipient UID or GID has write access to the
parent directory of the mailbox file.
Use the "postconf -l" command to find out what locking methods
Postfix supports on your system.
virtual_mailbox_limit
An upper limit on the size of a mailbox file or maildir file.
Example 1: using the virtual delivery agent for all local mail
==============================================================
This example does not use the Postfix local delivery agent at all.
With this configuration Postfix does no alias expansion, no .forward
file expansion, no lookups of recipients in /etc/passwd, and allows
but ignores user+foo address extensions.
Instead of "hash" specify "dbm" or "btree", depending on your system
type. The command "postconf -m" displays possible lookup table
types.
/etc/postfix/main.cf:
# Don't send mail to the local delivery agent.
mydestination =
# All domains that are listed in $virtual_mailbox_domains
# are delivered via $virtual_transport, which is the virtual
# delivery agent by default.
virtual_mailbox_domains =
$myhostname localhost.$mydomain virtual1.domain virtual2.domain
virtual_transport = virtual
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_minimum_uid = 100
virtual_uid_maps = hash:/etc/postfix/vuid
virtual_gid_maps = hash:/etc/postfix/vgid
Define a virtual delivery agent if the entry doesn't already exist:
/etc/postfix/master.cf:
virtual unix - n n - - virtual
Example recipients, one UNIX-style mailbox, one qmail-style maildir:
/etc/postfix/vmailbox:
test1@virtual1.domain test1
test2@virtual2.domain test2/
/etc/postfix/vuid:
test1@virtual1.domain 5001
test2@virtual2.domain 5002
/etc/postfix/vgid:
test1@virtual1.domain 5001
test2@virtual2.domain 5002
Execute something like the following commands for each mailbox recipient:
# touch /var/mail/vhosts/test1
# chown 5001:5001 /var/mail/vhosts/test1
Execute something like the following commands for each maildir recipient:
# mkdir /var/mail/vhosts/test2
# chown 5002:5002 /var/mail/vhosts/test2
Be sure to make the necessary entries for root@$myhostname,
postmaster@$myhostname and for any other necessary addresses.
Example 2: co-existing with the default local delivery agent
============================================================
In this example, the default Postfix local delivery agent handles
the mail for non-virtual recipients; the virtual delivery agent
handles virtual recipients, and all virtual mailboxes are owned
by user ID 5000, group ID 5000.
Instead of "hash" specify "dbm" or "btree", depending on your system
type. The command "postconf -m" displays possible lookup table
types.
/etc/postfix/main.cf:
# All domains and users delivered by the virtual local delivery agent.
virtual_transport = virtual
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_mailbox_domains = $virtual_mailbox_maps
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# All domains and users delivered by the local delivery agent.
# local_recipient_maps is used by the SMTP server to reject mail
# for unknown users.
local_transport = local
mydestination = $myhostname $localhost.$mydomain
local_recipient_maps = unix:passwd.byname $alias_maps
Define a virtual delivery agent if the entry doesn't already exist:
/etc/postfix/master.cf:
virtual unix - n n - - virtual
Example recipients, one UNIX-style mailbox, one qmail-style maildir:
/etc/postfix/vmailbox:
test1@virtual1.domain test1
test2@virtual2.domain test2/
/etc/postfix/vmaildomains:
virtual1.domain required to prevent relay access denied errors
virtual2.domain required to prevent relay access denied errors
Execute something like the following commands for each mailbox recipient:
# touch /var/mail/vhosts/test1
# chown 5000:5000 /var/mail/vhosts/test1
Execute something like the following commands for each maildir recipient:
# mkdir /var/mail/vhosts/test2
# chown 5000:5000 /var/mail/vhosts/test2
Remember that each domain is required to have a postmaster contact
address.
Example 3: hosting many virtual users
Non-UNIX accounts, domains not shared
=====================================
Example 2 is fine if you host only a few virtual users. With many
users you will want to separate the information that changes often
(the user addresses) from the information that changes rarely (the
names of hosted domains).
This example is the same as above, with co-existing local and
virtual domains, but it uses a separate table for specifying the
virtual domain names.
/etc/postfix/main.cf:
# All domains and users delivered by the virtual local delivery agent.
virtual_transport = virtual
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_mailbox_domains = hash:/etc/postfix/vmaildomains
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# All domains and users delivered by the local delivery agent.
# local_recipient_maps is used by the SMTP server to reject mail
# for unknown users.
local_transport = local
mydestination = $myhostname $localhost.$mydomain
local_recipient_maps = unix:passwd.byname $alias_maps
Define a virtual delivery agent if the entry doesn't already exist:
/etc/postfix/master.cf:
virtual unix - n n - - virtual
Example recipients, one UNIX-style mailbox, one qmail-style maildir:
/etc/postfix/vmailbox:
test1@virtual1.domain test1
test2@virtual2.domain test2/
/etc/postfix/vmaildomains:
virtual1.domain required to prevent relay access denied errors
virtual2.domain required to prevent relay access denied errors
Execute something like the following commands for each mailbox recipient:
# touch /var/mail/vhosts/test1
# chown 5000:5000 /var/mail/vhosts/test1
Execute something like the following commands for each maildir recipient:
# mkdir /var/mail/vhosts/test2
# chown 5000:5000 /var/mail/vhosts/test2
Remember that each domain is required to have a postmaster contact
address.
Example 4: forwarding mail for an old account to a new address
==============================================================
In order to forward mail for a user who no longer exists, one would
set up a rule in a virtual table (please ignore the text in the
virtual configuration file about virtual domains):
/etc/postfix/main.cf:
virtual_maps = hash:/etc/postfix/virtual
/etc/postfix/virtual:
old_user@old.domain new_user@new.domain
Example 5: setting up a virtual vacation autoresponder
======================================================
In order to set up an autoreply for virtual recipients while still
delivering mail as normal, set up a rule in a virtual table (please
ignore the text in the virtual configuration file about virtual
domains):
/etc/postfix/main.cf:
virtual_maps = hash:/etc/postfix/virtual
/etc/postfix/virtual:
user@domain.tld user@domain.tld, user@autoreply.domain.tld
This delivers mail to the recipient, and sends a copy of the mail
to the address that produces automatic replies. The address can be
serviced on a different machine, or it can be serviced locally by
setting up a transport map entry that pipes all mail for the
autoreply.domain.tld into some script that sends an automatic
reply back to the sender.
Aliases, mailing lists, etc.
============================

View File

@@ -1,13 +1,3 @@
===============================================================
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
===============================================================
The sender/recipient address verification code is lightly documented
and has been tested lightly. The code is proof-of-concept quality
and must not be used on high-volume sites. Use at your own risk.
===============================================================
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
===============================================================
In the text below, incompatible changes are labeled with the Postfix
snapshot that introduced the change. If you upgrade from a later
Postfix version, then you do not have to worry about that particular
@@ -26,15 +16,16 @@ Incompatible changes with Postfix snapshot 2.0.16-2004XXXX
==========================================================
The SMTP server no longer accept sender addresses that match a
local, virtual or relay domain while the address is not listed in
the corresponding local, virtual or relay recipient table.
local, virtual or relay domain while the address is not listed as
valid in the corresponding local, virtual or relay recipient table.
This is not configurable.
Incompatible changes with Postfix snapshot 2.0.16-20031226
==========================================================
Postfix no longer allows mail addresses with bare numeric IP
addresses (user@1.2.3.4). The form user@[ipaddress] is still
allowed.
addresses (user@1.2.3.4). This is not configurable. The form
user@[ipaddress] is still allowed.
Bounce messages now have a separate queue life time. This is
controlled by the bounce_queue_lifetime parameter. The default is

View File

@@ -155,9 +155,9 @@ mail_owner = postfix
#
# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS".
#
#mydestination = $myhostname, localhost.$mydomain
#mydestination = $myhostname, localhost.$mydomain $mydomain
#mydestination = $myhostname, localhost.$mydomain, $mydomain,
#mydestination = $myhostname, localhost.$mydomain, localhost
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
# mail.$mydomain, www.$mydomain, ftp.$mydomain
# REJECTING MAIL FOR UNKNOWN LOCAL USERS

View File

@@ -82,7 +82,7 @@ smtp inet n - n - - smtpd
pickup fifo n - n 60 1 pickup
cleanup unix n - n - 0 cleanup
qmgr fifo n - n 300 1 qmgr
#qmgr fifo n - n 300 1 nqmgr
#qmgr fifo n - n 300 1 oqmgr
rewrite unix - - n - - trivial-rewrite
bounce unix - - n - 0 bounce
defer unix - - n - 0 bounce

View File

@@ -24,10 +24,27 @@
# The general form of a PCRE table is:
#
# /pattern/flags result
# When pattern matches the input string, use the cor-
# responding result value.
#
# !/pattern/flags result
# When pattern matches (does not match) a search
# string, use the corresponding result value.
# When pattern does not match the input string, use
# the corresponding result value.
#
# if /pattern/flags
#
# endif Match the input string against the patterns between
# if and endif, if and only if the input string also
# matches pattern. The if..endif can nest.
#
# Note: do not prepend whitespace to patterns inside
# if..endif.
#
# if !/pattern/flags
#
# endif Match the input string against the patterns between
# if and endif, if and only if the input string does
# not match pattern. The if..endif can nest.
#
# blank lines and comments
# Empty lines and whitespace-only lines are ignored,
@@ -39,20 +56,6 @@
# line that starts with whitespace continues a logi-
# cal line.
#
# if /pattern/flags
#
# endif
#
# if !/pattern/flags
#
# endif Match the search string against the patterns
# between if and endif, if and only if the search
# string matches (does not match) pattern. The
# if..endif can nest.
#
# Note: do not prepend whitespace to patterns inside
# if..endif.
#
# Each pattern is a perl-like regular expression. The
# expression delimiter can be any character, except whites-
# pace or characters that have special meaning (tradition-
@@ -126,10 +129,10 @@
#
# SEARCH ORDER
# Patterns are applied in the order as specified in the
# table, until a pattern is found that matches the search
# table, until a pattern is found that matches the input
# string.
#
# Each pattern is applied to the entire lookup key string.
# Each pattern is applied to the entire input string.
# Depending on the application, that string is an entire
# client hostname, an entire client IP address, or an entire
# mail address. Thus, no parent domain or parent network

View File

@@ -447,7 +447,7 @@ test -n "$create" && {
test -n "$set_permission" && {
chown $recursive $owner $path || exit 1
test -z "$group" || chgrp $recursive $group $path || exit 1
chmod $mode $path || exit 1
chmod $recursive $mode $path || exit 1
}
done
}

View File

@@ -22,7 +22,7 @@
# File types:
# d=directory
# f=regular file
# l=symlink to $sendmail_path
# l=symlink
#
# File flags:
# No flag means the flag is not active.
@@ -55,15 +55,15 @@ $queue_directory/maildrop:d:$mail_owner:$setgid_group:730:uc
$queue_directory/public:d:$mail_owner:$setgid_group:710:uc
$queue_directory/pid:d:root:-:755:uc
$queue_directory/trace:d:$mail_owner:-:700:ucr
$daemon_directory/anvil:f:root:-:755
$daemon_directory/bounce:f:root:-:755
$daemon_directory/cleanup:f:root:-:755
$daemon_directory/anvil:f:root:-:755
$daemon_directory/error:f:root:-:755
$daemon_directory/flush:f:root:-:755
$daemon_directory/lmtp:f:root:-:755
$daemon_directory/local:f:root:-:755
$daemon_directory/master:f:root:-:755
$daemon_directory/nqmgr:f:root:-:755
$daemon_directory/oqmgr:f:root:-:755
$daemon_directory/pickup:f:root:-:755
$daemon_directory/pipe:f:root:-:755
$daemon_directory/proxymap:f:root:-:755
@@ -76,6 +76,7 @@ $daemon_directory/spawn:f:root:-:755
$daemon_directory/trivial-rewrite:f:root:-:755
$daemon_directory/verify:f:root:-:755
$daemon_directory/virtual:f:root:-:755
$nqmgr_path:l:root:-:755
$command_directory/postalias:f:root:-:755
$command_directory/postcat:f:root:-:755
$command_directory/postconf:f:root:-:755
@@ -144,7 +145,8 @@ $manpage_directory/man8/flush.8:f:root:-:644
$manpage_directory/man8/lmtp.8:f:root:-:644
$manpage_directory/man8/local.8:f:root:-:644
$manpage_directory/man8/master.8:f:root:-:644
$manpage_directory/man8/nqmgr.8:f:root:-:644
$manpage_directory/man8/nqmgr.8:f:root:-:644:o
$manpage_directory/man8/oqmgr.8:f:root:-:644:
$manpage_directory/man8/pickup.8:f:root:-:644
$manpage_directory/man8/pipe.8:f:root:-:644
$manpage_directory/man8/proxymap.8:f:root:-:644
@@ -216,6 +218,7 @@ $readme_directory/SMTPD_PROXY_README:f:root:-:644
$readme_directory/ULTRIX_README:f:root:-:644
$readme_directory/UUCP_README:f:root:-:644
$readme_directory/VERP_README:f:root:-:644
$readme_directory/VIRTUAL_MAILBOX_README:f:root:-:644
$readme_directory/VIRTUAL_README:f:root:-:644
$readme_directory/XCLIENT_README:f:root:-:644
$readme_directory/XFORWARD_README:f:root:-:644

View File

@@ -24,10 +24,27 @@
# The general form of a Postfix regular expression table is:
#
# /pattern/flags result
# When pattern matches the input string, use the cor-
# responding result value.
#
# !/pattern/flags result
# When pattern matches (does not match) a search
# string, use the corresponding result value.
# When pattern does not match the input string, use
# the corresponding result value.
#
# if /pattern/flags
#
# endif Match the input string against the patterns between
# if and endif, if and only if that same input string
# also matches pattern. The if..endif can nest.
#
# Note: do not prepend whitespace to patterns inside
# if..endif.
#
# if !/pattern/flags
#
# endif Match the input string against the patterns between
# if and endif, if and only if that same input string
# does not match pattern. The if..endif can nest.
#
# blank lines and comments
# Empty lines and whitespace-only lines are ignored,
@@ -39,20 +56,6 @@
# line that starts with whitespace continues a logi-
# cal line.
#
# if /pattern/flags
#
# endif
#
# if !/pattern/flags
#
# endif Match the search string against the patterns
# between if and endif, if and only if the search
# string matches (does not match) pattern. The
# if..endif can nest.
#
# Note: do not prepend whitespace to patterns inside
# if..endif.
#
# Each pattern is a regular expression enclosed by a pair of
# delimiters. The regular expression syntax is described in
# re_format(7). The expression delimiter can be any charac-
@@ -60,18 +63,33 @@
# meaning (traditionally the forward slash is used). The
# regular expression can contain whitespace.
#
# By default, matching is case-insensitive, although follow-
# ing the second slash with an `i' flag will reverse this.
# Other flags are `x' (disable extended expression syntax),
# and `m' (enable multi-line mode, that is, treat newline
# characters as special).
# By default, matching is case-insensitive, and newlines are
# not treated as special characters. The behavior is con-
# trolled by flags, which are toggled by appending one or
# more of the following characters after the pattern:
#
# i (default: on)
# Toggles the case sensitivity flag. By default,
# matching is case insensitive.
#
# x (default: on)
# Toggles the extended expression syntax flag. By
# default, support for extended expression syntax is
# enabled.
#
# m (default: off)
# Toggle the multi-line mode flag. When this flag is
# on, the ^ and $ metacharacters match immediately
# after and immediately before a newline character,
# respectively, in addition to matching at the start
# and end of the input string.
#
# TABLE SEARCH ORDER
# Patterns are applied in the order as specified in the
# table, until a pattern is found that matches the search
# table, until a pattern is found that matches the input
# string.
#
# Each pattern is applied to the entire lookup key string.
# Each pattern is applied to the entire input string.
# Depending on the application, that string is an entire
# client hostname, an entire client IP address, or an entire
# mail address. Thus, no parent domain or parent network

View File

@@ -69,6 +69,12 @@ smtpd_sasl_auth_enable = no
#smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_security_options = noanonymous
# The application name used for SASL server initialization. This
# controls the name of the SASL configuration file. The default value
# is smtpd, corresponding to a SASL configuration file named smtpd.conf.
#
smtpd_sasl_application_name = smtpd
# The smtpd_sasl_local_domain parameter specifies the name of the
# local authentication realm.
#

View File

@@ -27,15 +27,28 @@ additional_conditions = 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).
# Alternatively, you can override the default SELECT statement (and the
# above table, select_field, where_field, and additional_conditions) by
# specifying the query:
#query = select forw_addr from mxaliases where alias = '%s' and status = 'paid'
# Before the query is actually issued, all occurences of %s are replaced
# with the address to look up, %u are repleced with the user portion,
# and %d with the domain portion.
# 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:
# 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.
# This is equivalent to:
#
#query = select my_lookup_user_alias('%s')
#
# and overrides both the query parameter and the table-related fields
# above.
#
# 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

View File

@@ -498,8 +498,9 @@ smtpd_helo_restrictions =
# reject_unknown_sender_domain: reject sender domain without A or MX record.
# +reject_unlisted_sender: reject a sender address in a local, virtual or
# relay domain that is not listed as a valid address.
# This restriction is automatically added at the end if not explicitly
# specified.
# If not executed as part of smtpd_sender_restrictions, this restriction
# is automatically executed after smtpd_sender_restrictions evaluation
# completes.
# reject_rhsbl_sender domain.tld: reject sender domain name if it is listed
# in an A record under domain.tld.
# check_sender_access maptype:mapname
@@ -592,8 +593,9 @@ smtpd_sender_restrictions =
# reject_unknown_recipient_domain: reject domains without A or MX record.
# +reject_unlisted_recipient: reject a recipient address in a local, virtual
# or relay domain that is not listed as a valid address.
# This restriction is automatically added at the end if not explicitly
# specified.
# If not executed as part of smtpd_recipient_restrictions, this
# restriction is automatically executed after completion of
# smtpd_recipient_restrictions evaluation.
# +check_recipient_maps: a backwards compatibility alias for the
# reject_unlisted_recipient restriction.
# check_recipient_access maptype:mapname

View File

@@ -93,8 +93,8 @@ sub smtpd_access_policy {
open_database() unless $database_obj;
# Lookup the time stamp for this client/sender/recipient.
$key = $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
$key =~ tr /A-Z/a-z/;
$key =
lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
$time_stamp = read_database($key);
$now = time();

View File

@@ -5,7 +5,7 @@ SHELL = /bin/sh
DAEMONS = bounce.8.html cleanup.8.html defer.8.html error.8.html local.8.html \
lmtp.8.html master.8.html pickup.8.html pipe.8.html qmgr.8.html \
showq.8.html smtp.8.html smtpd.8.html trivial-rewrite.8.html \
nqmgr.8.html spawn.8.html flush.8.html virtual.8.html qmqpd.8.html \
oqmgr.8.html spawn.8.html flush.8.html virtual.8.html qmqpd.8.html \
trace.8.html verify.8.html proxymap.8.html anvil.8.html
COMMANDS= mailq.1.html newaliases.1.html postalias.1.html postcat.1.html \
postconf.1.html postfix.1.html postkick.1.html postlock.1.html \
@@ -66,11 +66,11 @@ master.8.html: ../src/master/master.c
PATH=../mantools:$$PATH; \
srctoman $? | $(AWK) | nroff -man | uniq | man2html | postlink >$@
nqmgr.8.html: ../src/nqmgr/qmgr.c
oqmgr.8.html: ../src/oqmgr/qmgr.c
PATH=../mantools:$$PATH; \
srctoman $? | sed -e 's/qmgr[^_]/n&/' \
-e 's/qmgr$$/n&/' \
-e 's/QMGR[^_]/N&/' | \
srctoman $? | sed -e 's/qmgr[^_]/o&/' \
-e 's/qmgr$$/o&/' \
-e 's/QMGR[^_]/O&/' | \
$(AWK) | nroff -man | uniq | man2html | postlink >$@
pickup.8.html: ../src/pickup/pickup.c

View File

@@ -1,14 +1,14 @@
<html> <body> <pre>
NQMGR(8) NQMGR(8)
OQMGR(8) OQMGR(8)
<b>NAME</b>
nqmgr - Postfix queue manager
oqmgr - old Postfix queue manager
<b>SYNOPSIS</b>
<b>nqmgr</b> [generic Postfix daemon options]
<b>oqmgr</b> [generic Postfix daemon options]
<b>DESCRIPTION</b>
The <b>nqmgr</b> daemon awaits the arrival of incoming mail and
The <b>oqmgr</b> daemon awaits the arrival of incoming mail and
arranges for its delivery via Postfix delivery processes.
The actual mail routing strategy is delegated to the <a href="trivial-rewrite.8.html"><b>triv-</b></a>
<a href="trivial-rewrite.8.html"><b>ial-rewrite</b>(8)</a> daemon. This program expects to be run
@@ -19,7 +19,7 @@ NQMGR(8) NQMGR(8)
by undeliverable bounce notifications.
<b>MAIL QUEUES</b>
The <b>nqmgr</b> daemon maintains the following queues:
The <b>oqmgr</b> daemon maintains the following queues:
<b>incoming</b>
Inbound mail from the network, or mail picked up by
@@ -44,7 +44,7 @@ NQMGR(8) NQMGR(8)
until someone sets them free.
<b>DELIVERY STATUS REPORTS</b>
The <b>nqmgr</b> daemon keeps an eye on per-message delivery sta-
The <b>oqmgr</b> daemon keeps an eye on per-message delivery sta-
tus reports in the following directories. Each status
report file has the same name as the corresponding message
file:
@@ -57,7 +57,7 @@ NQMGR(8) NQMGR(8)
delayed. These files are maintained by the
<a href="defer.8.html"><b>defer</b>(8)</a> daemon.
The <b>nqmgr</b> daemon is responsible for asking the <a href="bounce.8.html"><b>bounce</b>(8)</a>
The <b>oqmgr</b> daemon is responsible for asking the <a href="bounce.8.html"><b>bounce</b>(8)</a>
or <a href="defer.8.html"><b>defer</b>(8)</a> daemons to send non-delivery reports.
<b>STRATEGIES</b>
@@ -97,12 +97,6 @@ NQMGR(8) NQMGR(8)
attempts by maintaining a short-term, in-memory
list of unreachable destinations.
<b>preemptive message scheduling</b>
The queue manager attempts to minimize the average
per-recipient delay while still preserving the cor-
rect per-message delays, using a sophisticated pre-
emptive message scheduling.
<b>TRIGGERS</b>
On an idle system, the queue manager waits for the arrival
of trigger events, or it waits for a timer to go off. A
@@ -135,7 +129,7 @@ NQMGR(8) NQMGR(8)
ever. The action is to start an incoming queue
scan.
The <b>nqmgr</b> daemon reads an entire buffer worth of triggers.
The <b>oqmgr</b> daemon reads an entire buffer worth of triggers.
Multiple identical trigger requests are collapsed into
one, and trigger requests are sorted so that <b>A</b> and <b>F</b> pre-
cede <b>D</b> and <b>I</b>. Thus, in order to force a deferred queue
@@ -143,14 +137,14 @@ NQMGR(8) NQMGR(8)
manager of the arrival of new mail one would request <b>I</b>.
<b>STANDARDS</b>
None. The <b>nqmgr</b> daemon does not interact with the outside
None. The <b>oqmgr</b> daemon does not interact with the outside
world.
<b>SECURITY</b>
The <b>nqmgr</b> daemon is not security sensitive. It reads sin-
The <b>oqmgr</b> daemon is not security sensitive. It reads sin-
gle-character messages from untrusted local users, and
thus may be susceptible to denial of service attacks. The
<b>nqmgr</b> daemon does not talk to the outside world, and it
<b>oqmgr</b> daemon does not talk to the outside world, and it
can be run at fixed low privilege in a chrooted environ-
ment.
@@ -184,9 +178,6 @@ NQMGR(8) NQMGR(8)
Top-level directory of the Postfix queue.
<b>Active queue controls</b>
In the text below, <i>transport</i> is the first field in a <b>mas-</b>
<b>ter.cf</b> entry.
<b>qmgr_clog_warn_time</b>
Minimal delay between warnings that a specific des-
tination is clogging up the active queue. Specify 0
@@ -201,27 +192,6 @@ NQMGR(8) NQMGR(8)
This parameter also limits the size of the short-
term, in-memory destination cache.
<b>qmgr_message_recipient_minimum</b>
Per message minimum of in-memory recipients.
<b>default_recipient_limit</b>
Default limit on the number of in-memory recipients
per transport.
<i>transport</i><b>_recipient_limit</b>
Limit on the number of in-memory recipients, for
the named message <i>transport</i>.
<b>default_extra_recipient_limit</b>
Default limit on the total number of per transport
in-memory recipients that the preempting messages
can have.
<i>transport</i><b>_extra_recipient_limit</b>
Limit on the number of in-memory recipients which
all preempting messages delivered by the transport
<i>transport</i> can have.
<b>Timing controls</b>
<b>minimal_backoff_time</b>
Minimal time in seconds between delivery attempts
@@ -252,6 +222,25 @@ NQMGR(8) NQMGR(8)
ken delivery transport.
<b>Concurrency controls</b>
In the text below, <i>transport</i> is the first field in a <b>mas-</b>
<b>ter.cf</b> entry.
<b>qmgr_fudge_factor</b> (valid range: 10..100)
The percentage of delivery resources that a busy
mail system will use up for delivery of a large
mailing list message. With 100%, delivery of one
message does not begin before the previous message
has been delivered. This results in good perfor-
mance for large mailing lists, but results in poor
response time for one-to-one mail. With less than
100%, response time for one-to-one mail improves,
but large mailing list delivery performance suf-
fers. In the worst case, recipients near the begin-
ning of a large list receive a burst of messages
immediately, while recipients near the end of that
list receive that same burst of messages a whole
day later.
<b>initial_destination_concurrency</b>
Initial per-destination concurrency level for par-
allel delivery to the same destination.
@@ -274,52 +263,6 @@ NQMGR(8) NQMGR(8)
Limit on the number of recipients per message
transfer, for the named message <i>transport</i>.
<b>Message scheduling</b>
<i>transport</i><b>_delivery_slot_cost</b> (valid range: 0,2,3...)
This parameter basically controls how often a mes-
sage delivered by <i>transport</i> can be preempted by
another message. An internal per-message/transport
counter is incremented by one for each <i>trans-</i>
<i>port</i><b>_delivery_slot_cost</b> deliveries handled by
<i>transport</i>. This counter represents the number of
"available delivery slots" for use by other mes-
sages. Current message can be preempted by another
message when that other message can be delivered
using less <i>transport</i> agents than the value of the
"available delivery slots" counter.
Value equal to 0 disables the message preemption
for <i>transport</i>.
<i>transport</i><b>_minimum_delivery_slots</b>
Message preemption is not attempted at all whenever
a message that can't ever accumulate at least
<i>transport</i><b>_minimum_delivery_slots</b> available delivery
slots is being delivered by <i>transport</i>.
<i>transport</i><b>_delivery_slot_discount</b> (valid range: 0..100)
<i>transport</i><b>_delivery_slot_loan</b>
These parameters speed up the moment when a message
preemption can happen. Instead of waiting until
the full amount of delivery slots required is
available, the preemption can happen when <i>trans-</i>
<i>port</i><b>_delivery_slot_discount</b> percent of the required
amount plus <i>transport</i><b>_delivery_slot_loan</b> still
remains to be accumulated. Note that the full
amount will still have to be accumulated before
another preemption can take place later.
<b>default_delivery_slot_cost</b>
<b>default_minimum_delivery_slots</b>
<b>default_delivery_slot_discount</b>
<b>default_delivery_slot_loan</b>
Default values for the transport specific parame-
ters described above.
<b>SEE ALSO</b>
<a href="master.8.html">master(8)</a>, process manager
syslogd(8) system logging
@@ -335,10 +278,5 @@ NQMGR(8) NQMGR(8)
P.O. Box 704
Yorktown Heights, NY 10598, USA
Scheduler enhancements:
Patrik Rak
Modra 6
155 00, Prague, Czech Republic
NQMGR(8)
OQMGR(8)
</pre> </body> </html>

View File

@@ -25,10 +25,27 @@ PCRE_TABLE(5) PCRE_TABLE(5)
The general form of a PCRE table is:
<b>/</b><i>pattern</i><b>/</b><i>flags result</i>
When <i>pattern</i> matches the input string, use the cor-
responding <i>result</i> value.
<b>!/</b><i>pattern</i><b>/</b><i>flags result</i>
When <i>pattern</i> matches (does not match) a search
string, use the corresponding <i>result</i> value.
When <i>pattern</i> does <b>not</b> match the input string, use
the corresponding <i>result</i> value.
<b>if /</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b> Match the input string against the patterns between
<b>if</b> and <b>endif</b>, if and only if the input string also
matches <i>pattern</i>. The <b>if</b>..<b>endif</b> can nest.
Note: do not prepend whitespace to patterns inside
<b>if</b>..<b>endif</b>.
<b>if !/</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b> Match the input string against the patterns between
<b>if</b> and <b>endif</b>, if and only if the input string does
<b>not</b> match <i>pattern</i>. The <b>if</b>..<b>endif</b> can nest.
blank lines and comments
Empty lines and whitespace-only lines are ignored,
@@ -40,20 +57,6 @@ PCRE_TABLE(5) PCRE_TABLE(5)
line that starts with whitespace continues a logi-
cal line.
<b>if /</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b>
<b>if !/</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b> Match the search string against the patterns
between <b>if</b> and <b>endif</b>, if and only if the search
string matches (does not match) <i>pattern</i>. The
<b>if</b>..<b>endif</b> can nest.
Note: do not prepend whitespace to patterns inside
<b>if</b>..<b>endif</b>.
Each pattern is a perl-like regular expression. The
expression delimiter can be any character, except whites-
pace or characters that have special meaning (tradition-
@@ -127,10 +130,10 @@ PCRE_TABLE(5) PCRE_TABLE(5)
<b>SEARCH ORDER</b>
Patterns are applied in the order as specified in the
table, until a pattern is found that matches the search
table, until a pattern is found that matches the input
string.
Each pattern is applied to the entire lookup key string.
Each pattern is applied to the entire input string.
Depending on the application, that string is an entire
client hostname, an entire client IP address, or an entire
mail address. Thus, no parent domain or parent network

View File

@@ -1,4 +1,4 @@
<html> <head> </head> <body> <pre>
<html> <body> <pre>
PICKUP(8) PICKUP(8)
<b>NAME</b>
@@ -44,32 +44,32 @@ PICKUP(8) PICKUP(8)
command after a configuration change.
<b>Content inspection controls</b>
<b>content</b><i>_</i><b>filter</b>
<b>content_filter</b>
The name of a mail delivery transport that filters
mail and that either bounces mail or re-injects the
result back into Postfix. This parameter uses the
same syntax as the right-hand side of a Postfix
transport table.
<b>receive</b><i>_</i><b>override</b><i>_</i><b>options</b>
<b>receive_override_options</b>
The following options override <b>main.cf</b> settings.
The options are passed on to the downstream cleanup
server.
<b>no</b><i>_</i><b>address</b><i>_</i><b>mappings</b>
<b>no_address_mappings</b>
Disable canonical address mapping, virtual
alias map expansion, address masquerading,
and automatic BCC recipients. Specify this
if address mapping etc. are to be done <b>after</b>
an external content filter.
<b>no</b><i>_</i><b>header</b><i>_</i><b>body</b><i>_</i><b>checks</b>
<b>no_header_body_checks</b>
Disable header/body_checks. Specify this if
header/body_checks are to be done <b>after</b> an
external content filter.
<b>Miscellaneous</b>
<b>queue</b><i>_</i><b>directory</b>
<b>queue_directory</b>
Top-level directory of the Postfix queue.
<b>SEE ALSO</b>

View File

@@ -1,11 +1,11 @@
<html> <head> </head> <body> <pre>
<html> <body> <pre>
POSTDROP(1) POSTDROP(1)
<b>NAME</b>
postdrop - Postfix mail posting utility
<b>SYNOPSIS</b>
<b>postdrop</b> [<b>-rv</b>] [<b>-c</b> <i>config_dir</i>]
<b>postdrop</b> [<b>-rv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>]
<b>DESCRIPTION</b>
The <b>postdrop</b> command creates a file in the <b>maildrop</b> direc-
@@ -48,7 +48,7 @@ POSTDROP(1) POSTDROP(1)
A non-standard directory is allowed only if the
name is listed in the standard <b>main.cf</b> file, in the
<b>alternate</b><i>_</i><b>config</b><i>_</i><b>directories</b> configuration parame-
<b>alternate_config_directories</b> configuration parame-
ter value.
Only the superuser is allowed to specify arbitrary
@@ -58,21 +58,21 @@ POSTDROP(1) POSTDROP(1)
/var/spool/postfix, mail queue
/etc/postfix, configuration files
<b>CONFIGURATION</b> <b>PARAMETERS</b>
<b>CONFIGURATION PARAMETERS</b>
See the Postfix <b>main.cf</b> file for syntax details and for
default values. Use the <b>postfix</b> <b>reload</b> command after a
default values. Use the <b>postfix reload</b> command after a
configuration change.
<b>import</b><i>_</i><b>environment</b>
<b>import_environment</b>
List of names of environment parameters that can be
imported from non-Postfix processes.
<b>queue</b><i>_</i><b>directory</b>
<b>queue_directory</b>
Top-level directory of the Postfix queue. This is
also the root directory of Postfix daemons that run
chrooted.
<b>SEE</b> <b>ALSO</b>
<b>SEE ALSO</b>
<a href="sendmail.1.html">sendmail(1)</a> compatibility interface
syslogd(8) system logging

View File

@@ -97,6 +97,12 @@ QMGR(8) QMGR(8)
attempts by maintaining a short-term, in-memory
list of unreachable destinations.
<b>preemptive message scheduling</b>
The queue manager attempts to minimize the average
per-recipient delay while still preserving the cor-
rect per-message delays, using a sophisticated pre-
emptive message scheduling.
<b>TRIGGERS</b>
On an idle system, the queue manager waits for the arrival
of trigger events, or it waits for a timer to go off. A
@@ -177,6 +183,9 @@ QMGR(8) QMGR(8)
Top-level directory of the Postfix queue.
<b>Active queue controls</b>
In the text below, <i>transport</i> is the first field in a <b>mas-</b>
<b>ter.cf</b> entry.
<b>qmgr_clog_warn_time</b>
Minimal delay between warnings that a specific des-
tination is clogging up the active queue. Specify 0
@@ -191,6 +200,27 @@ QMGR(8) QMGR(8)
This parameter also limits the size of the short-
term, in-memory destination cache.
<b>qmgr_message_recipient_minimum</b>
Per message minimum of in-memory recipients.
<b>default_recipient_limit</b>
Default limit on the number of in-memory recipients
per transport.
<i>transport</i><b>_recipient_limit</b>
Limit on the number of in-memory recipients, for
the named message <i>transport</i>.
<b>default_extra_recipient_limit</b>
Default limit on the total number of per transport
in-memory recipients that the preempting messages
can have.
<i>transport</i><b>_extra_recipient_limit</b>
Limit on the number of in-memory recipients which
all preempting messages delivered by the transport
<i>transport</i> can have.
<b>Timing controls</b>
<b>minimal_backoff_time</b>
Minimal time in seconds between delivery attempts
@@ -221,25 +251,6 @@ QMGR(8) QMGR(8)
ken delivery transport.
<b>Concurrency controls</b>
In the text below, <i>transport</i> is the first field in a <b>mas-</b>
<b>ter.cf</b> entry.
<b>qmgr_fudge_factor</b> (valid range: 10..100)
The percentage of delivery resources that a busy
mail system will use up for delivery of a large
mailing list message. With 100%, delivery of one
message does not begin before the previous message
has been delivered. This results in good perfor-
mance for large mailing lists, but results in poor
response time for one-to-one mail. With less than
100%, response time for one-to-one mail improves,
but large mailing list delivery performance suf-
fers. In the worst case, recipients near the begin-
ning of a large list receive a burst of messages
immediately, while recipients near the end of that
list receive that same burst of messages a whole
day later.
<b>initial_destination_concurrency</b>
Initial per-destination concurrency level for par-
allel delivery to the same destination.
@@ -262,6 +273,52 @@ QMGR(8) QMGR(8)
Limit on the number of recipients per message
transfer, for the named message <i>transport</i>.
<b>Message scheduling</b>
<i>transport</i><b>_delivery_slot_cost</b> (valid range: 0,2,3...)
This parameter basically controls how often a mes-
sage delivered by <i>transport</i> can be preempted by
another message. An internal per-message/transport
counter is incremented by one for each <i>trans-</i>
<i>port</i><b>_delivery_slot_cost</b> deliveries handled by
<i>transport</i>. This counter represents the number of
"available delivery slots" for use by other mes-
sages. Current message can be preempted by another
message when that other message can be delivered
using less <i>transport</i> agents than the value of the
"available delivery slots" counter.
Value equal to 0 disables the message preemption
for <i>transport</i>.
<i>transport</i><b>_minimum_delivery_slots</b>
Message preemption is not attempted at all whenever
a message that can't ever accumulate at least
<i>transport</i><b>_minimum_delivery_slots</b> available delivery
slots is being delivered by <i>transport</i>.
<i>transport</i><b>_delivery_slot_discount</b> (valid range: 0..100)
<i>transport</i><b>_delivery_slot_loan</b>
These parameters speed up the moment when a message
preemption can happen. Instead of waiting until
the full amount of delivery slots required is
available, the preemption can happen when <i>trans-</i>
<i>port</i><b>_delivery_slot_discount</b> percent of the required
amount plus <i>transport</i><b>_delivery_slot_loan</b> still
remains to be accumulated. Note that the full
amount will still have to be accumulated before
another preemption can take place later.
<b>default_delivery_slot_cost</b>
<b>default_minimum_delivery_slots</b>
<b>default_delivery_slot_discount</b>
<b>default_delivery_slot_loan</b>
Default values for the transport specific parame-
ters described above.
<b>SEE ALSO</b>
<a href="master.8.html">master(8)</a>, process manager
syslogd(8) system logging
@@ -277,5 +334,10 @@ QMGR(8) QMGR(8)
P.O. Box 704
Yorktown Heights, NY 10598, USA
Scheduler enhancements:
Patrik Rak
Modra 6
155 00, Prague, Czech Republic
QMGR(8)
</pre> </body> </html>

View File

@@ -25,10 +25,27 @@ REGEXP_TABLE(5) REGEXP_TABLE(5)
The general form of a Postfix regular expression table is:
<b>/</b><i>pattern</i><b>/</b><i>flags result</i>
When <i>pattern</i> matches the input string, use the cor-
responding <i>result</i> value.
<b>!/</b><i>pattern</i><b>/</b><i>flags result</i>
When <i>pattern</i> matches (does not match) a search
string, use the corresponding <i>result</i> value.
When <i>pattern</i> does <b>not</b> match the input string, use
the corresponding <i>result</i> value.
<b>if /</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b> Match the input string against the patterns between
<b>if</b> and <b>endif</b>, if and only if that same input string
also matches <i>pattern</i>. The <b>if</b>..<b>endif</b> can nest.
Note: do not prepend whitespace to patterns inside
<b>if</b>..<b>endif</b>.
<b>if !/</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b> Match the input string against the patterns between
<b>if</b> and <b>endif</b>, if and only if that same input string
does <b>not</b> match <i>pattern</i>. The <b>if</b>..<b>endif</b> can nest.
blank lines and comments
Empty lines and whitespace-only lines are ignored,
@@ -40,20 +57,6 @@ REGEXP_TABLE(5) REGEXP_TABLE(5)
line that starts with whitespace continues a logi-
cal line.
<b>if /</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b>
<b>if !/</b><i>pattern</i><b>/</b><i>flags</i>
<b>endif</b> Match the search string against the patterns
between <b>if</b> and <b>endif</b>, if and only if the search
string matches (does not match) <i>pattern</i>. The
<b>if</b>..<b>endif</b> can nest.
Note: do not prepend whitespace to patterns inside
<b>if</b>..<b>endif</b>.
Each pattern is a regular expression enclosed by a pair of
delimiters. The regular expression syntax is described in
<i>re</i><b>_</b><i>format</i>(7). The expression delimiter can be any charac-
@@ -61,18 +64,33 @@ REGEXP_TABLE(5) REGEXP_TABLE(5)
meaning (traditionally the forward slash is used). The
regular expression can contain whitespace.
By default, matching is case-insensitive, although follow-
ing the second slash with an `i' flag will reverse this.
Other flags are `x' (disable extended expression syntax),
and `m' (enable multi-line mode, that is, treat newline
characters as special).
By default, matching is case-insensitive, and newlines are
not treated as special characters. The behavior is con-
trolled by flags, which are toggled by appending one or
more of the following characters after the pattern:
<b>i</b> (default: on)
Toggles the case sensitivity flag. By default,
matching is case insensitive.
<b>x</b> (default: on)
Toggles the extended expression syntax flag. By
default, support for extended expression syntax is
enabled.
<b>m</b> (default: off)
Toggle the multi-line mode flag. When this flag is
on, the <b>^</b> and <b>$</b> metacharacters match immediately
after and immediately before a newline character,
respectively, in addition to matching at the start
and end of the input string.
<b>TABLE SEARCH ORDER</b>
Patterns are applied in the order as specified in the
table, until a pattern is found that matches the search
table, until a pattern is found that matches the input
string.
Each pattern is applied to the entire lookup key string.
Each pattern is applied to the entire input string.
Depending on the application, that string is an entire
client hostname, an entire client IP address, or an entire
mail address. Thus, no parent domain or parent network

View File

@@ -92,16 +92,6 @@ header rewriting code.
<p>
<dt> <b>extract_recipient_limit</b> (default: 10240 recipients)
<dd> How many recipients Postfix will extract from message headers
before it gives up. This limits the damage that a run-away program
can do with "sendmail -t".
</dl>
<p>
The following parameters restrict the use of file system storage:
<dl>

View File

@@ -138,6 +138,13 @@ SMTPD(8) SMTPD(8)
explicitly selected at program build time and
explicitly enabled at runtime.
<b>smtpd_sasl_application_name</b>
The application name used for SASL server initial-
ization. This controls the name of the SASL con-
figuration file. The default value is <i>smtpd</i>, cor-
responding to a SASL configuration file named
<i>smtpd.conf</i>.
<b>smtpd_sasl_local_domain</b>
The name of the local authentication realm.

View File

@@ -5,7 +5,7 @@ SHELL = /bin/sh
DAEMONS = man8/bounce.8 man8/defer.8 man8/cleanup.8 man8/error.8 man8/local.8 \
man8/lmtp.8 man8/master.8 man8/pickup.8 man8/pipe.8 man8/qmgr.8 \
man8/showq.8 man8/smtp.8 man8/smtpd.8 man8/trivial-rewrite.8 \
man8/nqmgr.8 man8/spawn.8 man8/flush.8 man8/virtual.8 man8/qmqpd.8 \
man8/oqmgr.8 man8/spawn.8 man8/flush.8 man8/virtual.8 man8/qmqpd.8 \
man8/verify.8 man8/trace.8 man8/proxymap.8 man8/anvil.8
COMMANDS= man1/postalias.1 man1/postcat.1 man1/postconf.1 man1/postfix.1 \
man1/postkick.1 man1/postlock.1 man1/postlog.1 man1/postdrop.1 \
@@ -57,11 +57,11 @@ man8/lmtp.8: ../src/lmtp/lmtp.c
man8/master.8: ../src/master/master.c
../mantools/srctoman $? >$@
man8/nqmgr.8: ../src/nqmgr/qmgr.c
man8/oqmgr.8: ../src/oqmgr/qmgr.c
../mantools/srctoman $? | \
sed -e 's/qmgr[^_]/n&/' \
-e 's/qmgr$$/n&/' \
-e 's/QMGR[^_]/N&/' >$@
sed -e 's/qmgr[^_]/o&/' \
-e 's/qmgr$$/o&/' \
-e 's/QMGR[^_]/O&/' >$@
man8/pickup.8: ../src/pickup/pickup.c
../mantools/srctoman $? >$@

View File

@@ -31,25 +31,30 @@ described in the SYNOPSIS above.
.fi
The general form of a PCRE table is:
.IP "\fB/\fIpattern\fB/\fIflags result\fR"
.IP "\fB!/\fIpattern\fB/\fIflags result\fR"
When \fIpattern\fR matches (does not match) a search string, use
When \fIpattern\fR matches the input string, use
the corresponding \fIresult\fR value.
.IP "\fB!/\fIpattern\fB/\fIflags result\fR"
When \fIpattern\fR does \fBnot\fR match the input string, use
the corresponding \fIresult\fR value.
.IP "\fBif /\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
Match the input string against the patterns between \fBif\fR
and \fBendif\fR, if and only if the input string also matches
\fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
.sp
Note: do not prepend whitespace to patterns inside
\fBif\fR..\fBendif\fR.
.IP "\fBif !/\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
Match the input string against the patterns between \fBif\fR
and \fBendif\fR, if and only if the input string does \fBnot\fR
match \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
.IP "blank lines and comments"
Empty lines and whitespace-only lines are ignored, as
are lines whose first non-whitespace character is a `#'.
.IP "multi-line text"
A logical line starts with non-whitespace text. A line that
starts with whitespace continues a logical line.
.IP "\fBif /\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
.IP "\fBif !/\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
Match the search string against the patterns between \fBif\fR
and \fBendif\fR, if and only if the search string matches (does
not match) \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
.sp
Note: do not prepend whitespace to patterns inside
\fBif\fR..\fBendif\fR.
.PP
Each pattern is a perl-like regular expression. The expression
delimiter can be any character, except whitespace or characters
@@ -113,9 +118,9 @@ error, thus reserving these combinations for future expansion.
.ad
.fi
Patterns are applied in the order as specified in the table, until a
pattern is found that matches the search string.
pattern is found that matches the input string.
Each pattern is applied to the entire lookup key string.
Each pattern is applied to the entire input string.
Depending on the application, that string is an entire client
hostname, an entire client IP address, or an entire mail address.
Thus, no parent domain or parent network search is done, and

View File

@@ -31,25 +31,30 @@ described in the SYNOPSIS above.
.fi
The general form of a Postfix regular expression table is:
.IP "\fB/\fIpattern\fB/\fIflags result\fR"
.IP "\fB!/\fIpattern\fB/\fIflags result\fR"
When \fIpattern\fR matches (does not match) a search string,
When \fIpattern\fR matches the input string,
use the corresponding \fIresult\fR value.
.IP "\fB!/\fIpattern\fB/\fIflags result\fR"
When \fIpattern\fR does \fBnot\fR match the input string,
use the corresponding \fIresult\fR value.
.IP "\fBif /\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
Match the input string against the patterns between \fBif\fR
and \fBendif\fR, if and only if that same input string also
matches \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
.sp
Note: do not prepend whitespace to patterns inside
\fBif\fR..\fBendif\fR.
.IP "\fBif !/\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
Match the input string against the patterns between \fBif\fR
and \fBendif\fR, if and only if that same input string does
\fBnot\fR match \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
.IP "blank lines and comments"
Empty lines and whitespace-only lines are ignored, as
are lines whose first non-whitespace character is a `#'.
.IP "multi-line text"
A logical line starts with non-whitespace text. A line that
starts with whitespace continues a logical line.
.IP "\fBif /\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
.IP "\fBif !/\fIpattern\fB/\fIflags\fR"
.IP "\fBendif\fR"
Match the search string against the patterns between \fBif\fR
and \fBendif\fR, if and only if the search string matches (does
not match) \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
.sp
Note: do not prepend whitespace to patterns inside
\fBif\fR..\fBendif\fR.
.PP
Each pattern is a regular expression enclosed by a pair of delimiters.
The regular expression syntax is described in \fIre_format\fR(7).
@@ -57,19 +62,30 @@ The expression delimiter can be any character, except whitespace
or characters that have special meaning (traditionally the forward
slash is used). The regular expression can contain whitespace.
By default, matching is case-insensitive, although following
the second slash with an `i' flag will reverse this. Other flags
are `x' (disable extended expression syntax), and `m' (enable
multi-line mode, that is, treat newline characters as special).
By default, matching is case-insensitive, and newlines are not
treated as special characters. The behavior is controlled by flags,
which are toggled by appending one or more of the following
characters after the pattern:
.IP "\fBi\fR (default: on)"
Toggles the case sensitivity flag. By default, matching is case
insensitive.
.IP "\fBx\fR (default: on)"
Toggles the extended expression syntax flag. By default, support
for extended expression syntax is enabled.
.IP "\fBm\fR (default: off)"
Toggle the multi-line mode flag. When this flag is on, the \fB^\fR
and \fB$\fR metacharacters match immediately after and immediately
before a newline character, respectively, in addition to
matching at the start and end of the input string.
.SH TABLE SEARCH ORDER
.na
.nf
.ad
.fi
Patterns are applied in the order as specified in the table, until a
pattern is found that matches the search string.
pattern is found that matches the input string.
Each pattern is applied to the entire lookup key string.
Each pattern is applied to the entire input string.
Depending on the application, that string is an entire client
hostname, an entire client IP address, or an entire mail address.
Thus, no parent domain or parent network search is done, and

View File

@@ -1,18 +1,18 @@
.TH NQMGR 8
.TH OQMGR 8
.ad
.fi
.SH NAME
nqmgr
oqmgr
\-
Postfix queue manager
old Postfix queue manager
.SH SYNOPSIS
.na
.nf
\fBnqmgr\fR [generic Postfix daemon options]
\fBoqmgr\fR [generic Postfix daemon options]
.SH DESCRIPTION
.ad
.fi
The \fBnqmgr\fR daemon awaits the arrival of incoming mail
The \fBoqmgr\fR daemon awaits the arrival of incoming mail
and arranges for its delivery via Postfix delivery processes.
The actual mail routing strategy is delegated to the
\fBtrivial-rewrite\fR(8) daemon.
@@ -27,7 +27,7 @@ undeliverable bounce notifications.
.nf
.ad
.fi
The \fBnqmgr\fR daemon maintains the following queues:
The \fBoqmgr\fR daemon maintains the following queues:
.IP \fBincoming\fR
Inbound mail from the network, or mail picked up by the
local \fBpickup\fR agent from the \fBmaildrop\fR directory.
@@ -49,7 +49,7 @@ sets them free.
.nf
.ad
.fi
The \fBnqmgr\fR daemon keeps an eye on per-message delivery status
The \fBoqmgr\fR daemon keeps an eye on per-message delivery status
reports in the following directories. Each status report file has
the same name as the corresponding message file:
.IP \fBbounce\fR
@@ -59,7 +59,7 @@ These files are maintained by the \fBbounce\fR(8) daemon.
Per-recipient status information about why mail is delayed.
These files are maintained by the \fBdefer\fR(8) daemon.
.PP
The \fBnqmgr\fR daemon is responsible for asking the
The \fBoqmgr\fR daemon is responsible for asking the
\fBbounce\fR(8) or \fBdefer\fR(8) daemons to send non-delivery
reports.
.SH STRATEGIES
@@ -92,10 +92,6 @@ attempt.
.IP "\fBdestination status cache\fR"
The queue manager avoids unnecessary delivery attempts by
maintaining a short-term, in-memory list of unreachable destinations.
.IP "\fBpreemptive message scheduling\fR"
The queue manager attempts to minimize the average per-recipient delay
while still preserving the correct per-message delays, using
a sophisticated preemptive message scheduling.
.SH TRIGGERS
.na
.nf
@@ -123,7 +119,7 @@ Wakeup call, This is used by the master server to instantiate
servers that should not go away forever. The action is to start
an incoming queue scan.
.PP
The \fBnqmgr\fR daemon reads an entire buffer worth of triggers.
The \fBoqmgr\fR daemon reads an entire buffer worth of triggers.
Multiple identical trigger requests are collapsed into one, and
trigger requests are sorted so that \fBA\fR and \fBF\fR precede
\fBD\fR and \fBI\fR. Thus, in order to force a deferred queue run,
@@ -134,15 +130,15 @@ of the arrival of new mail one would request \fBI\fR.
.nf
.ad
.fi
None. The \fBnqmgr\fR daemon does not interact with the outside world.
None. The \fBoqmgr\fR daemon does not interact with the outside world.
.SH SECURITY
.na
.nf
.ad
.fi
The \fBnqmgr\fR daemon is not security sensitive. It reads
The \fBoqmgr\fR daemon is not security sensitive. It reads
single-character messages from untrusted local users, and thus may
be susceptible to denial of service attacks. The \fBnqmgr\fR daemon
be susceptible to denial of service attacks. The \fBoqmgr\fR daemon
does not talk to the outside world, and it can be run at fixed low
privilege in a chrooted environment.
.SH DIAGNOSTICS
@@ -179,8 +175,6 @@ Top-level directory of the Postfix queue.
.SH "Active queue controls"
.ad
.fi
In the text below, \fItransport\fR is the first field in a
\fBmaster.cf\fR entry.
.IP \fBqmgr_clog_warn_time\fR
Minimal delay between warnings that a specific destination
is clogging up the active queue. Specify 0 to disable.
@@ -191,19 +185,6 @@ Limit the number of in-memory recipients.
.sp
This parameter also limits the size of the short-term, in-memory
destination cache.
.IP \fBqmgr_message_recipient_minimum\fR
Per message minimum of in-memory recipients.
.IP \fBdefault_recipient_limit\fR
Default limit on the number of in-memory recipients per transport.
.IP \fItransport\fB_recipient_limit\fR
Limit on the number of in-memory recipients, for the named
message \fItransport\fR.
.IP \fBdefault_extra_recipient_limit\fR
Default limit on the total number of per transport in-memory
recipients that the preempting messages can have.
.IP \fItransport\fB_extra_recipient_limit\fR
Limit on the number of in-memory recipients which all preempting
messages delivered by the transport \fItransport\fR can have.
.SH "Timing controls"
.ad
.fi
@@ -231,6 +212,19 @@ delivery transport.
.SH "Concurrency controls"
.ad
.fi
In the text below, \fItransport\fR is the first field in a
\fBmaster.cf\fR entry.
.IP "\fBqmgr_fudge_factor\fR (valid range: 10..100)"
The percentage of delivery resources that a busy mail system will
use up for delivery of a large mailing list message.
With 100%, delivery of one message does not begin before the previous
message has been delivered. This results in good performance for large
mailing lists, but results in poor response time for one-to-one mail.
With less than 100%, response time for one-to-one mail improves,
but large mailing list delivery performance suffers. In the worst
case, recipients near the beginning of a large list receive a burst
of messages immediately, while recipients near the end of that list
receive that same burst of messages a whole day later.
.IP \fBinitial_destination_concurrency\fR
Initial per-destination concurrency level for parallel delivery
to the same destination.
@@ -248,40 +242,6 @@ Default limit on the number of recipients per message transfer.
.IP \fItransport\fB_destination_recipient_limit\fR
Limit on the number of recipients per message transfer, for the
named message \fItransport\fR.
.SH "Message scheduling"
.ad
.fi
.IP "\fItransport\fB_delivery_slot_cost\fR (valid range: 0,2,3...)
This parameter basically controls how often a message
delivered by \fItransport\fR can be preempted by another
message.
An internal per-message/transport counter is incremented by one
for each \fItransport\fB_delivery_slot_cost\fR
deliveries handled by \fItransport\fR. This counter represents
the number of "available delivery slots" for use by other messages.
Current message can be preempted by another message when that
other message can be delivered using less \fItransport\fR agents
than the value of the "available delivery slots" counter.
.sp
Value equal to 0 disables the message preemption for \fItransport\fR.
.IP \fItransport\fB_minimum_delivery_slots\fR
Message preemption is not attempted at all whenever a message
that can't ever accumulate at least \fItransport\fB_minimum_delivery_slots\fR
available delivery slots is being delivered by \fItransport\fR.
.IP "\fItransport\fB_delivery_slot_discount\fR (valid range: 0..100)"
.IP \fItransport\fB_delivery_slot_loan\fR
These parameters speed up the moment when a message preemption can happen.
Instead of waiting until the full amount of delivery slots
required is available, the preemption can happen when
\fItransport\fB_delivery_slot_discount\fR percent of the required
amount plus \fItransport\fB_delivery_slot_loan\fR still remains to
be accumulated. Note that the full amount will still have to be
accumulated before another preemption can take place later.
.IP \fBdefault_delivery_slot_cost\fR
.IP \fBdefault_minimum_delivery_slots\fR
.IP \fBdefault_delivery_slot_discount\fR
.IP \fBdefault_delivery_slot_loan\fR
Default values for the transport specific parameters described above.
.SH SEE ALSO
.na
.nf
@@ -301,8 +261,3 @@ Wietse Venema
IBM T.J. Watson Research
P.O. Box 704
Yorktown Heights, NY 10598, USA
Scheduler enhancements:
Patrik Rak
Modra 6
155 00, Prague, Czech Republic

View File

@@ -92,6 +92,10 @@ attempt.
.IP "\fBdestination status cache\fR"
The queue manager avoids unnecessary delivery attempts by
maintaining a short-term, in-memory list of unreachable destinations.
.IP "\fBpreemptive message scheduling\fR"
The queue manager attempts to minimize the average per-recipient delay
while still preserving the correct per-message delays, using
a sophisticated preemptive message scheduling.
.SH TRIGGERS
.na
.nf
@@ -175,6 +179,8 @@ Top-level directory of the Postfix queue.
.SH "Active queue controls"
.ad
.fi
In the text below, \fItransport\fR is the first field in a
\fBmaster.cf\fR entry.
.IP \fBqmgr_clog_warn_time\fR
Minimal delay between warnings that a specific destination
is clogging up the active queue. Specify 0 to disable.
@@ -185,6 +191,19 @@ Limit the number of in-memory recipients.
.sp
This parameter also limits the size of the short-term, in-memory
destination cache.
.IP \fBqmgr_message_recipient_minimum\fR
Per message minimum of in-memory recipients.
.IP \fBdefault_recipient_limit\fR
Default limit on the number of in-memory recipients per transport.
.IP \fItransport\fB_recipient_limit\fR
Limit on the number of in-memory recipients, for the named
message \fItransport\fR.
.IP \fBdefault_extra_recipient_limit\fR
Default limit on the total number of per transport in-memory
recipients that the preempting messages can have.
.IP \fItransport\fB_extra_recipient_limit\fR
Limit on the number of in-memory recipients which all preempting
messages delivered by the transport \fItransport\fR can have.
.SH "Timing controls"
.ad
.fi
@@ -212,19 +231,6 @@ delivery transport.
.SH "Concurrency controls"
.ad
.fi
In the text below, \fItransport\fR is the first field in a
\fBmaster.cf\fR entry.
.IP "\fBqmgr_fudge_factor\fR (valid range: 10..100)"
The percentage of delivery resources that a busy mail system will
use up for delivery of a large mailing list message.
With 100%, delivery of one message does not begin before the previous
message has been delivered. This results in good performance for large
mailing lists, but results in poor response time for one-to-one mail.
With less than 100%, response time for one-to-one mail improves,
but large mailing list delivery performance suffers. In the worst
case, recipients near the beginning of a large list receive a burst
of messages immediately, while recipients near the end of that list
receive that same burst of messages a whole day later.
.IP \fBinitial_destination_concurrency\fR
Initial per-destination concurrency level for parallel delivery
to the same destination.
@@ -242,6 +248,40 @@ Default limit on the number of recipients per message transfer.
.IP \fItransport\fB_destination_recipient_limit\fR
Limit on the number of recipients per message transfer, for the
named message \fItransport\fR.
.SH "Message scheduling"
.ad
.fi
.IP "\fItransport\fB_delivery_slot_cost\fR (valid range: 0,2,3...)
This parameter basically controls how often a message
delivered by \fItransport\fR can be preempted by another
message.
An internal per-message/transport counter is incremented by one
for each \fItransport\fB_delivery_slot_cost\fR
deliveries handled by \fItransport\fR. This counter represents
the number of "available delivery slots" for use by other messages.
Current message can be preempted by another message when that
other message can be delivered using less \fItransport\fR agents
than the value of the "available delivery slots" counter.
.sp
Value equal to 0 disables the message preemption for \fItransport\fR.
.IP \fItransport\fB_minimum_delivery_slots\fR
Message preemption is not attempted at all whenever a message
that can't ever accumulate at least \fItransport\fB_minimum_delivery_slots\fR
available delivery slots is being delivered by \fItransport\fR.
.IP "\fItransport\fB_delivery_slot_discount\fR (valid range: 0..100)"
.IP \fItransport\fB_delivery_slot_loan\fR
These parameters speed up the moment when a message preemption can happen.
Instead of waiting until the full amount of delivery slots
required is available, the preemption can happen when
\fItransport\fB_delivery_slot_discount\fR percent of the required
amount plus \fItransport\fB_delivery_slot_loan\fR still remains to
be accumulated. Note that the full amount will still have to be
accumulated before another preemption can take place later.
.IP \fBdefault_delivery_slot_cost\fR
.IP \fBdefault_minimum_delivery_slots\fR
.IP \fBdefault_delivery_slot_discount\fR
.IP \fBdefault_delivery_slot_loan\fR
Default values for the transport specific parameters described above.
.SH SEE ALSO
.na
.nf
@@ -261,3 +301,8 @@ Wietse Venema
IBM T.J. Watson Research
P.O. Box 704
Yorktown Heights, NY 10598, USA
Scheduler enhancements:
Patrik Rak
Modra 6
155 00, Prague, Czech Republic

View File

@@ -131,6 +131,11 @@ real-time SMTP-based content filter.
Enable per-session authentication as per RFC 2554 (SASL).
This functionality is available only when explicitly selected
at program build time and explicitly enabled at runtime.
.IP \fBsmtpd_sasl_application_name\fR
The application name used for SASL server initialization. This
controls the name of the SASL configuration file. The default
value is \fIsmtpd\fR, corresponding to a SASL configuration file
named \fIsmtpd.conf\fR.
.IP \fBsmtpd_sasl_local_domain\fR
The name of the local authentication realm.
.IP \fBsmtpd_sasl_security_options\fR

View File

@@ -263,6 +263,14 @@ compare_or_symlink() {
}
}
compare_or_hardlink() {
(cmp $1 $2 >/dev/null 2>&1 && echo Skipping $2...) || {
echo Updating $2...
rm -f $2 || exit 1
ln $1 $2 || exit 1
}
}
check_parent() {
for path
do
@@ -623,6 +631,9 @@ do
'$mailq_path')
check_parent $MAILQ_PATH || exit 1
compare_or_symlink $SENDMAIL_PATH $MAILQ_PATH || exit 1;;
'$nqmgr_path')
compare_or_hardlink $DAEMON_DIRECTORY/qmgr \
$DAEMON_DIRECTORY/nqmgr || exit 1;;
'$newaliases_path')
check_parent $NEWALIASES_PATH || exit 1
compare_or_symlink $SENDMAIL_PATH $NEWALIASES_PATH || exit 1;;

View File

@@ -23,25 +23,30 @@
# .fi
# The general form of a PCRE table is:
# .IP "\fB/\fIpattern\fB/\fIflags result\fR"
# .IP "\fB!/\fIpattern\fB/\fIflags result\fR"
# When \fIpattern\fR matches (does not match) a search string, use
# When \fIpattern\fR matches the input string, use
# the corresponding \fIresult\fR value.
# .IP "\fB!/\fIpattern\fB/\fIflags result\fR"
# When \fIpattern\fR does \fBnot\fR match the input string, use
# the corresponding \fIresult\fR value.
# .IP "\fBif /\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# Match the input string against the patterns between \fBif\fR
# and \fBendif\fR, if and only if the input string also matches
# \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
# .sp
# Note: do not prepend whitespace to patterns inside
# \fBif\fR..\fBendif\fR.
# .IP "\fBif !/\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# Match the input string against the patterns between \fBif\fR
# and \fBendif\fR, if and only if the input string does \fBnot\fR
# match \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
# .IP "blank lines and comments"
# Empty lines and whitespace-only lines are ignored, as
# are lines whose first non-whitespace character is a `#'.
# .IP "multi-line text"
# A logical line starts with non-whitespace text. A line that
# starts with whitespace continues a logical line.
# .IP "\fBif /\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# .IP "\fBif !/\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# Match the search string against the patterns between \fBif\fR
# and \fBendif\fR, if and only if the search string matches (does
# not match) \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
# .sp
# Note: do not prepend whitespace to patterns inside
# \fBif\fR..\fBendif\fR.
# .PP
# Each pattern is a perl-like regular expression. The expression
# delimiter can be any character, except whitespace or characters
@@ -103,9 +108,9 @@
# .ad
# .fi
# Patterns are applied in the order as specified in the table, until a
# pattern is found that matches the search string.
# pattern is found that matches the input string.
#
# Each pattern is applied to the entire lookup key string.
# Each pattern is applied to the entire input string.
# Depending on the application, that string is an entire client
# hostname, an entire client IP address, or an entire mail address.
# Thus, no parent domain or parent network search is done, and

View File

@@ -23,25 +23,30 @@
# .fi
# The general form of a Postfix regular expression table is:
# .IP "\fB/\fIpattern\fB/\fIflags result\fR"
# .IP "\fB!/\fIpattern\fB/\fIflags result\fR"
# When \fIpattern\fR matches (does not match) a search string,
# When \fIpattern\fR matches the input string,
# use the corresponding \fIresult\fR value.
# .IP "\fB!/\fIpattern\fB/\fIflags result\fR"
# When \fIpattern\fR does \fBnot\fR match the input string,
# use the corresponding \fIresult\fR value.
# .IP "\fBif /\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# Match the input string against the patterns between \fBif\fR
# and \fBendif\fR, if and only if that same input string also
# matches \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
# .sp
# Note: do not prepend whitespace to patterns inside
# \fBif\fR..\fBendif\fR.
# .IP "\fBif !/\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# Match the input string against the patterns between \fBif\fR
# and \fBendif\fR, if and only if that same input string does
# \fBnot\fR match \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
# .IP "blank lines and comments"
# Empty lines and whitespace-only lines are ignored, as
# are lines whose first non-whitespace character is a `#'.
# .IP "multi-line text"
# A logical line starts with non-whitespace text. A line that
# starts with whitespace continues a logical line.
# .IP "\fBif /\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# .IP "\fBif !/\fIpattern\fB/\fIflags\fR"
# .IP "\fBendif\fR"
# Match the search string against the patterns between \fBif\fR
# and \fBendif\fR, if and only if the search string matches (does
# not match) \fIpattern\fR. The \fBif\fR..\fBendif\fR can nest.
# .sp
# Note: do not prepend whitespace to patterns inside
# \fBif\fR..\fBendif\fR.
# .PP
# Each pattern is a regular expression enclosed by a pair of delimiters.
# The regular expression syntax is described in \fIre_format\fR(7).
@@ -49,17 +54,28 @@
# or characters that have special meaning (traditionally the forward
# slash is used). The regular expression can contain whitespace.
#
# By default, matching is case-insensitive, although following
# the second slash with an `i' flag will reverse this. Other flags
# are `x' (disable extended expression syntax), and `m' (enable
# multi-line mode, that is, treat newline characters as special).
# By default, matching is case-insensitive, and newlines are not
# treated as special characters. The behavior is controlled by flags,
# which are toggled by appending one or more of the following
# characters after the pattern:
# .IP "\fBi\fR (default: on)"
# Toggles the case sensitivity flag. By default, matching is case
# insensitive.
# .IP "\fBx\fR (default: on)"
# Toggles the extended expression syntax flag. By default, support
# for extended expression syntax is enabled.
# .IP "\fBm\fR (default: off)"
# Toggle the multi-line mode flag. When this flag is on, the \fB^\fR
# and \fB$\fR metacharacters match immediately after and immediately
# before a newline character, respectively, in addition to
# matching at the start and end of the input string.
# TABLE SEARCH ORDER
# .ad
# .fi
# Patterns are applied in the order as specified in the table, until a
# pattern is found that matches the search string.
# pattern is found that matches the input string.
#
# Each pattern is applied to the entire lookup key string.
# Each pattern is applied to the entire input string.
# Depending on the application, that string is an entire client
# hostname, an entire client IP address, or an entire mail address.
# Thus, no parent domain or parent network search is done, and

View File

@@ -1,72 +1,73 @@
SHELL = /bin/sh
SRCS = been_here.c bounce.c canon_addr.c cleanup_strerror.c clnt_stream.c \
debug_peer.c debug_process.c defer.c deliver_completed.c \
deliver_flock.c deliver_pass.c deliver_request.c domain_list.c \
dot_lockfile.c dot_lockfile_as.c ext_prop.c file_id.c \
header_opts.c is_header.c mail_addr.c mail_addr_crunch.c \
mail_addr_find.c mail_addr_map.c mail_command_server.c \
mail_command_client.c mail_conf.c mail_conf_bool.c mail_conf_int.c \
mail_conf_raw.c mail_conf_str.c mail_connect.c mail_copy.c \
mail_date.c mail_error.c mail_flush.c mail_open_ok.c mail_params.c \
mail_pathname.c mail_queue.c mail_run.c \
mail_scan_dir.c mail_stream.c mail_task.c mail_trigger.c maps.c \
mark_corrupt.c mkmap_db.c mkmap_dbm.c mkmap_open.c mynetworks.c \
mypwd.c namadr_list.c off_cvt.c opened.c own_inet_addr.c \
pipe_command.c post_mail.c quote_821_local.c \
SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \
canon_addr.c cfg_parser.c cleanup_strerror.c cleanup_strflags.c \
clnt_stream.c debug_peer.c debug_process.c defer.c \
deliver_completed.c deliver_flock.c deliver_pass.c deliver_request.c \
dict_ldap.c dict_mysql.c dict_pgsql.c dict_proxy.c domain_list.c \
dot_lockfile.c dot_lockfile_as.c ext_prop.c file_id.c flush_clnt.c \
header_opts.c header_token.c hold_message.c input_transp.c \
is_header.c log_adhoc.c mail_addr.c mail_addr_crunch.c \
mail_addr_find.c mail_addr_map.c mail_command_client.c \
mail_command_server.c mail_conf.c mail_conf_bool.c mail_conf_int.c \
mail_conf_raw.c mail_conf_str.c mail_conf_time.c mail_connect.c \
mail_copy.c mail_date.c mail_dict.c mail_error.c mail_flush.c \
mail_open_ok.c mail_params.c mail_pathname.c mail_queue.c \
mail_run.c mail_scan_dir.c mail_stream.c mail_task.c mail_trigger.c \
maps.c mark_corrupt.c match_parent_style.c mbox_conf.c \
mbox_open.c mime_state.c mkmap_db.c mkmap_dbm.c mkmap_open.c \
mynetworks.c mypwd.c namadr_list.c off_cvt.c opened.c \
own_inet_addr.c pipe_command.c post_mail.c quote_821_local.c \
quote_822_local.c rec_streamlf.c rec_type.c recipient_list.c \
record.c remove.c resolve_clnt.c resolve_local.c rewrite_clnt.c \
sent.c smtp_stream.c split_addr.c string_list.c sys_exits.c \
timed_ipc.c tok822_find.c tok822_node.c tok822_parse.c \
tok822_resolve.c tok822_rewrite.c tok822_tree.c xtext.c bounce_log.c \
flush_clnt.c mail_conf_time.c mbox_conf.c mbox_open.c abounce.c \
verp_sender.c match_parent_style.c mime_state.c header_token.c \
strip_addr.c virtual8_maps.c hold_message.c verify_clnt.c \
trace.c log_adhoc.c verify.c dict_proxy.c mail_dict.c input_transp.c \
cleanup_strflags.c anvil_clnt.c
OBJS = been_here.o bounce.o canon_addr.o cleanup_strerror.o clnt_stream.o \
debug_peer.o debug_process.o defer.o deliver_completed.o \
deliver_flock.o deliver_pass.o deliver_request.o domain_list.o \
dot_lockfile.o dot_lockfile_as.o ext_prop.o file_id.o \
header_opts.o is_header.o mail_addr.o mail_addr_crunch.o \
mail_addr_find.o mail_addr_map.o mail_command_server.o \
mail_command_client.o mail_conf.o mail_conf_bool.o mail_conf_int.o \
mail_conf_raw.o mail_conf_str.o mail_connect.o mail_copy.o \
mail_date.o mail_error.o mail_flush.o mail_open_ok.o mail_params.o \
mail_pathname.o mail_queue.o mail_run.o \
mail_scan_dir.o mail_stream.o mail_task.o mail_trigger.o maps.o \
mark_corrupt.o mkmap_db.o mkmap_dbm.o mkmap_open.o mynetworks.o \
mypwd.o namadr_list.o off_cvt.o opened.o own_inet_addr.o \
pipe_command.o post_mail.o quote_821_local.o \
sent.c smtp_stream.c split_addr.c string_list.c strip_addr.c \
sys_exits.c timed_ipc.c tok822_find.c tok822_node.c tok822_parse.c \
tok822_resolve.c tok822_rewrite.c tok822_tree.c trace.c verify.c \
verify_clnt.c verp_sender.c virtual8_maps.c xtext.c
OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \
clnt_stream.o debug_peer.o debug_process.o defer.o \
deliver_completed.o deliver_flock.o deliver_pass.o deliver_request.o \
dict_ldap.o dict_mysql.o dict_pgsql.o dict_proxy.o domain_list.o \
dot_lockfile.o dot_lockfile_as.o ext_prop.o file_id.o flush_clnt.o \
header_opts.o header_token.o hold_message.o input_transp.o \
is_header.o log_adhoc.o mail_addr.o mail_addr_crunch.o \
mail_addr_find.o mail_addr_map.o mail_command_client.o \
mail_command_server.o mail_conf.o mail_conf_bool.o mail_conf_int.o \
mail_conf_raw.o mail_conf_str.o mail_conf_time.o mail_connect.o \
mail_copy.o mail_date.o mail_dict.o mail_error.o mail_flush.o \
mail_open_ok.o mail_params.o mail_pathname.o mail_queue.o \
mail_run.o mail_scan_dir.o mail_stream.o mail_task.o mail_trigger.o \
maps.o mark_corrupt.o match_parent_style.o mbox_conf.o \
mbox_open.o mime_state.o mkmap_db.o mkmap_dbm.o mkmap_open.o \
mynetworks.o mypwd.o namadr_list.o off_cvt.o opened.o \
own_inet_addr.o pipe_command.o post_mail.o quote_821_local.o \
quote_822_local.o rec_streamlf.o rec_type.o recipient_list.o \
record.o remove.o resolve_clnt.o resolve_local.o rewrite_clnt.o \
sent.o smtp_stream.o split_addr.o string_list.o sys_exits.o \
timed_ipc.o tok822_find.o tok822_node.o tok822_parse.o \
tok822_resolve.o tok822_rewrite.o tok822_tree.o xtext.o bounce_log.o \
flush_clnt.o mail_conf_time.o mbox_conf.o mbox_open.o abounce.o \
verp_sender.o match_parent_style.o mime_state.o header_token.o \
strip_addr.o virtual8_maps.o hold_message.o verify_clnt.o \
trace.o log_adhoc.o verify.o dict_proxy.o mail_dict.o input_transp.o \
cleanup_strflags.o anvil_clnt.o
HDRS = been_here.h bounce.h canon_addr.h cleanup_user.h clnt_stream.h \
config.h debug_peer.h debug_process.h defer.h deliver_completed.h \
deliver_flock.h deliver_pass.h deliver_request.h domain_list.h \
dot_lockfile.h dot_lockfile_as.h ext_prop.h file_id.h \
header_opts.h is_header.h mail_addr.h mail_addr_crunch.h \
sent.o smtp_stream.o split_addr.o string_list.o strip_addr.o \
sys_exits.o timed_ipc.o tok822_find.o tok822_node.o tok822_parse.o \
tok822_resolve.o tok822_rewrite.o tok822_tree.o trace.o verify.o \
verify_clnt.o verp_sender.o virtual8_maps.o xtext.o
HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \
canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \
debug_peer.h debug_process.h defer.h deliver_completed.h \
deliver_flock.h deliver_pass.h deliver_request.h dict_ldap.h \
dict_mysql.h dict_pgsql.h dict_proxy.h domain_list.h dot_lockfile.h \
dot_lockfile_as.h ext_prop.h file_id.h flush_clnt.h header_opts.h \
header_token.h hold_message.h input_transp.h is_header.h \
lex_822.h log_adhoc.h mail_addr.h mail_addr_crunch.h \
mail_addr_find.h mail_addr_map.h mail_conf.h mail_copy.h \
mail_date.h mail_error.h mail_flush.h mail_open_ok.h mail_params.h \
mail_proto.h mail_queue.h mail_run.h mail_scan_dir.h mail_stream.h \
mail_task.h mail_version.h maps.h mark_corrupt.h mkmap.h \
mynetworks.h mypwd.h namadr_list.h off_cvt.h opened.h \
own_inet_addr.h pipe_command.h post_mail.h \
quote_821_local.h quote_822_local.h rec_streamlf.h rec_type.h \
recipient_list.h record.h resolve_clnt.h resolve_local.h \
rewrite_clnt.h sent.h smtp_stream.h split_addr.h string_list.h \
sys_exits.h timed_ipc.h tok822.h xtext.h bounce_log.h flush_clnt.h \
mbox_conf.h mbox_open.h abounce.h qmqp_proto.h verp_sender.h \
match_parent_style.h quote_flags.h mime_state.h header_token.h \
lex_822.h strip_addr.h virtual8_maps.h hold_message.h verify_clnt.h \
trace.h log_adhoc.h verify.h dict_proxy.h mail_dict.h qmgr_user.h \
input_transp.h anvil_clnt.h
mail_date.h mail_dict.h mail_error.h mail_flush.h mail_open_ok.h \
mail_params.h mail_proto.h mail_queue.h mail_run.h mail_scan_dir.h \
mail_stream.h mail_task.h mail_version.h maps.h mark_corrupt.h \
match_parent_style.h mbox_conf.h mbox_open.h mime_state.h \
mkmap.h mynetworks.h mypwd.h namadr_list.h off_cvt.h opened.h \
own_inet_addr.h pipe_command.h post_mail.h qmgr_user.h \
qmqp_proto.h quote_821_local.h quote_822_local.h quote_flags.h \
rec_streamlf.h rec_type.h recipient_list.h record.h resolve_clnt.h \
resolve_local.h rewrite_clnt.h sent.h smtp_stream.h split_addr.h \
string_list.h strip_addr.h sys_exits.h timed_ipc.h tok822.h \
trace.h verify.h verify_clnt.h verp_sender.h virtual8_maps.h \
xtext.h
TESTSRC = rec2stream.c stream2rec.c recdump.c
DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
CFLAGS = $(DEBUG) $(OPT) $(DEFS)
@@ -442,6 +443,17 @@ canon_addr.o: ../../include/vbuf.h
canon_addr.o: ../../include/mymalloc.h
canon_addr.o: rewrite_clnt.h
canon_addr.o: canon_addr.h
cfg_parser.o: cfg_parser.c
cfg_parser.o: ../../include/sys_defs.h
cfg_parser.o: ../../include/msg.h
cfg_parser.o: ../../include/mymalloc.h
cfg_parser.o: ../../include/vstring.h
cfg_parser.o: ../../include/vbuf.h
cfg_parser.o: ../../include/dict.h
cfg_parser.o: ../../include/vstream.h
cfg_parser.o: ../../include/argv.h
cfg_parser.o: mail_conf.h
cfg_parser.o: cfg_parser.h
cleanup_strerror.o: cleanup_strerror.c
cleanup_strerror.o: ../../include/sys_defs.h
cleanup_strerror.o: ../../include/vstring.h
@@ -546,6 +558,12 @@ deliver_request.o: ../../include/attr.h
deliver_request.o: mail_open_ok.h
deliver_request.o: recipient_list.h
deliver_request.o: deliver_request.h
dict_ldap.o: dict_ldap.c
dict_ldap.o: ../../include/sys_defs.h
dict_mysql.o: dict_mysql.c
dict_mysql.o: ../../include/sys_defs.h
dict_pgsql.o: dict_pgsql.c
dict_pgsql.o: ../../include/sys_defs.h
dict_proxy.o: dict_proxy.c
dict_proxy.o: ../../include/sys_defs.h
dict_proxy.o: ../../include/msg.h
@@ -803,7 +821,9 @@ mail_dict.o: ../../include/vstream.h
mail_dict.o: ../../include/vbuf.h
mail_dict.o: ../../include/argv.h
mail_dict.o: ../../include/msg.h
mail_dict.o: dict_proxy.h
mail_dict.o: dict_ldap.h
mail_dict.o: dict_mysql.h
mail_dict.o: dict_pgsql.h
mail_dict.o: mail_dict.h
mail_error.o: mail_error.c
mail_error.o: ../../include/sys_defs.h

View File

@@ -0,0 +1,298 @@
/*++
/* NAME
/* cfg_parser 3
/* SUMMARY
/* configuration parser utilities
/* SYNOPSIS
/* #include "cfg_parser.h"
/*
/* CFG_PARSER *cfg_parser_alloc(pname)
/* const char *pname;
/*
/* CFG_PARSER *cfg_parser_free(parser)
/* CFG_PARSER *parser;
/*
/* char *cfg_get_str(parser, name, defval, min, max)
/* const CFG_PARSER *parser;
/* const char *name;
/* const char *defval;
/* int min;
/* int max;
/*
/* int cfg_get_int(parser, name, defval, min, max)
/* const CFG_PARSER *parser;
/* const char *name;
/* int defval;
/* int min;
/* int max;
/*
/* int cfg_get_bool(parser, name, defval)
/* const CFG_PARSER *parser;
/* const char *name;
/* int defval;
/* DESCRIPTION
/* This module implements utilities for parsing parameters defined
/* either as "\fIname\fR = \fBvalue\fR" in a file pointed to by
/* \fIpname\fR (the old MySQL style), or as "\fIpname\fR_\fIname\fR =
/* \fBvalue\fR" in main.cf (the old LDAP style). It unifies the
/* two styles and provides support for range checking.
/*
/* \fIcfg_parser_alloc\fR initializes the parser.
/*
/* \fIcfg_parser_free\fR releases the parser.
/*
/* \fIcfg_get_str\fR looks up a string.
/*
/* \fIcfg_get_int\fR looks up an integer.
/*
/* \fIcfg_get_bool\fR looks up a boolean value.
/*
/* \fIdefval\fR is returned when no value was found. \fImin\fR is
/* zero or specifies a lower limit on the integer value or string
/* length; \fImax\fR is zero or specifies an upper limit on the
/* integer value or string length.
/*
/* Conveniently, \fIcfg_get_str\fR returns \fBNULL\fR if
/* \fIdefval\fR is \fBNULL\fR and no value was found. The returned
/* string has to be freed by the caller if not \fBNULL\fR.
/* DIAGNOSTICS
/* Fatal errors: bad string length, malformed numerical value, malformed
/* boolean value.
/* SEE ALSO
/* mail_conf_str(3) string-valued global configuration parameter support
/* mail_conf_int(3) integer-valued configuration parameter support
/* mail_conf_bool(3) boolean-valued configuration parameter support
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Liviu Daia
/* Institute of Mathematics of the Romanian Academy
/* P.O. BOX 1-764
/* RO-014700 Bucharest, ROMANIA
/*--*/
/* System library. */
#include "sys_defs.h"
#include <stdio.h>
#include <string.h>
/* Utility library. */
#include "msg.h"
#include "mymalloc.h"
#include "vstring.h"
#include "dict.h"
/* Global library. */
#include "mail_conf.h"
/* Application-specific. */
#include "cfg_parser.h"
/* get string from file */
static char *get_dict_str(const struct CFG_PARSER *parser,
const char *name, const char *defval,
int min, int max)
{
const char *strval;
int len;
if ((strval = (char *) dict_lookup(parser->name, name)) == 0)
strval = defval;
len = strlen(strval);
if (min && len < min)
msg_fatal("%s: bad string length %d < %d: %s = %s",
parser->name, len, min, name, strval);
if (max && len > max)
msg_fatal("%s: bad string length %d > %d: %s = %s",
parser->name, len, max, name, strval);
return (mystrdup(strval));
}
/* get string from main.cf */
static char *get_main_str(const struct CFG_PARSER *parser,
const char *name, const char *defval,
int min, int max)
{
static VSTRING *buf = 0;
if (buf == 0)
buf = vstring_alloc(15);
vstring_sprintf(buf, "%s_%s", parser->name, name);
return ((char *) get_mail_conf_str(vstring_str(buf), defval, min, max));
}
/* get integer from file */
static int get_dict_int(const struct CFG_PARSER *parser,
const char *name, int defval, int min, int max)
{
const char *strval;
int intval;
char junk;
if ((strval = (char *) dict_lookup(parser->name, name)) != 0) {
if (sscanf(strval, "%d%c", &intval, &junk) != 1)
msg_fatal("%s: bad numerical configuration: %s = %s",
parser->name, name, strval);
} else
intval = defval;
if (min && intval < min)
msg_fatal("%s: invalid %s parameter value %d < %d",
parser->name, name, intval, min);
if (max && intval > max)
msg_fatal("%s: invalid %s parameter value %d > %d",
parser->name, name, intval, max);
return (intval);
}
/* get integer from main.cf */
static int get_main_int(const struct CFG_PARSER *parser,
const char *name, int defval, int min, int max)
{
static VSTRING *buf = 0;
if (buf == 0)
buf = vstring_alloc(15);
vstring_sprintf(buf, "%s_%s", parser->name, name);
return (get_mail_conf_int(vstring_str(buf), defval, min, max));
}
/* get boolean option from file */
static int get_dict_bool(const struct CFG_PARSER *parser,
const char *name, int defval)
{
const char *strval;
int intval;
if ((strval = (char *) dict_lookup(parser->name, name)) != 0) {
if (strcasecmp(strval, CONFIG_BOOL_YES) == 0) {
intval = 1;
} else if (strcasecmp(strval, CONFIG_BOOL_NO) == 0) {
intval = 0;
} else {
msg_fatal("%s: bad boolean configuration: %s = %s",
parser->name, name, strval);
}
} else
intval = defval;
return (intval);
}
/* get boolean option from main.cf */
static int get_main_bool(const struct CFG_PARSER *parser,
const char *name, int defval)
{
static VSTRING *buf = 0;
if (buf == 0)
buf = vstring_alloc(15);
vstring_sprintf(buf, "%s_%s", parser->name, name);
return (get_mail_conf_bool(vstring_str(buf), defval));
}
/* initialize parser */
CFG_PARSER *cfg_parser_alloc(const char *pname)
{
const char *myname = "cfg_parser_alloc";
CFG_PARSER *parser;
if (pname == 0 || *pname == 0)
msg_fatal("%s: null parser name", myname);
parser = (CFG_PARSER *) mymalloc(sizeof(*parser));
parser->name = mystrdup(pname);
if (*parser->name == '/' || *parser->name == '.') {
dict_load_file(parser->name, parser->name);
parser->get_str = get_dict_str;
parser->get_int = get_dict_int;
parser->get_bool = get_dict_bool;
} else {
parser->get_str = get_main_str;
parser->get_int = get_main_int;
parser->get_bool = get_main_bool;
}
return (parser);
}
/* get string */
char *cfg_get_str(const CFG_PARSER *parser, const char *name,
const char *defval, int min, int max)
{
const char *myname = "cfg_get_str";
char *strval;
strval = parser->get_str(parser, name, (defval ? defval : ""), min, max);
if (defval == 0 && *strval == 0) {
/* the caller wants NULL instead of "" */
myfree(strval);
strval = 0;
}
if (msg_verbose)
msg_info("%s: %s: %s = %s", myname, parser->name, name,
(strval ? strval : "<NULL>"));
return (strval);
}
/* get integer */
int cfg_get_int(const CFG_PARSER *parser, const char *name, int defval,
int min, int max)
{
const char *myname = "cfg_get_int";
int intval;
intval = parser->get_int(parser, name, defval, min, max);
if (msg_verbose)
msg_info("%s: %s: %s = %d", myname, parser->name, name, intval);
return (intval);
}
/* get boolean option */
int cfg_get_bool(const CFG_PARSER *parser, const char *name, int defval)
{
const char *myname = "cfg_get_bool";
int intval;
intval = parser->get_bool(parser, name, defval);
if (msg_verbose)
msg_info("%s: %s: %s = %s", myname, parser->name, name,
(intval ? "on" : "off"));
return (intval);
}
/* release parser */
CFG_PARSER *cfg_parser_free(CFG_PARSER *parser)
{
const char *myname = "cfg_parser_free";
if (parser->name == 0 || *parser->name == 0)
msg_panic("%s: null parser name", myname);
if (*parser->name == '/' || *parser->name == '.') {
if (dict_handle(parser->name))
dict_unregister(parser->name);
}
myfree(parser->name);
myfree((char *) parser);
return (0);
}

View File

@@ -0,0 +1,49 @@
#ifndef _CFG_PARSER_H_INCLUDED_
#define _CFG_PARSER_H_INCLUDED_
/*++
/* NAME
/* cfg_parser 3h
/* SUMMARY
/* configuration parser utilities
/* SYNOPSIS
/* #include "cfg_parser.h"
DESCRIPTION
.nf
/*
* External interface.
*/
typedef struct CFG_PARSER {
char *name;
char *(*get_str) (const struct CFG_PARSER *, const char *, const char *,
int, int);
int (*get_int) (const struct CFG_PARSER *, const char *, int, int, int);
int (*get_bool) (const struct CFG_PARSER *, const char *, int);
} CFG_PARSER;
extern CFG_PARSER *cfg_parser_alloc(const char *);
extern char *cfg_get_str(const CFG_PARSER *, const char *, const char *,
int, int);
extern int cfg_get_int(const CFG_PARSER *, const char *, int, int, int);
extern int cfg_get_bool(const CFG_PARSER *, const char *, int);
extern CFG_PARSER *cfg_parser_free(CFG_PARSER *);
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Liviu Daia
/* Institute of Mathematics of the Romanian Academy
/* P.O. BOX 1-764
/* RO-014700 Bucharest, ROMANIA
/*--*/
#endif

View File

@@ -135,9 +135,19 @@
/* John Hensley
/* john@sunislelodge.com
/*
/* LaMont Jones
/* lamont@hp.com
/* Current maintainers:
/*
/* LaMont Jones
/* lamont@debian.org
/*
/* Victor Duchovni
/* Morgan Stanley
/* New York, USA
/*
/* Liviu Daia
/* Institute of Mathematics of the Romanian Academy
/* P.O. BOX 1-764
/* RO-014700 Bucharest, ROMANIA
/*--*/
/* System library. */
@@ -155,6 +165,7 @@
#include <ldap.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
/*
* Older APIs have weird memory freeing behavior.
@@ -179,13 +190,16 @@
#include "mymalloc.h"
#include "vstring.h"
#include "dict.h"
#include "dict_ldap.h"
#include "stringops.h"
#include "binhash.h"
/* AAARGH!! */
/* Global library. */
#include "../global/mail_conf.h"
#include "cfg_parser.h"
/* Application-specific. */
#include "dict_ldap.h"
typedef struct {
LDAP *conn_ld;
@@ -198,6 +212,7 @@ typedef struct {
*/
typedef struct {
DICT dict; /* generic member */
CFG_PARSER *parser;
char *ldapsource;
char *server_host;
int server_port;
@@ -238,12 +253,6 @@ typedef struct {
static BINHASH *conn_hash = 0;
typedef struct {
char *(*get_str) (const char *, const char *, const char *, int, int);
int (*get_int) (const char *, const char *, int, int, int);
int (*get_bool) (const char *, const char *, int);
} CFG_PARSER;
#if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT)
/*
* LDAP connection timeout support.
@@ -273,105 +282,6 @@ static void dict_ldap_logprint(LDAP_CONST char *data)
myfree(buf);
}
static char *dict_ldap_get_dict_str(const char *opt_dict_name,
const char *name, const char *defval,
int min, int max)
{
const char *strval;
int len;
if ((strval = (char *) dict_lookup(opt_dict_name, name)) == 0)
strval = defval;
len = strlen(strval);
if (min && len < min)
msg_fatal("%s: bad string length %d < %d: %s = %s",
opt_dict_name, len, min, name, strval);
if (max && len > max)
msg_fatal("%s: bad string length %d > %d: %s = %s",
opt_dict_name, len, max, name, strval);
return (mystrdup(strval));
}
static char *dict_ldap_get_mail_str(const char *opt_dict_name,
const char *name, const char *defval,
int min, int max)
{
static VSTRING *buf = 0;
if (buf == 0)
buf = vstring_alloc(15);
vstring_sprintf(buf, "%s_%s", opt_dict_name, name);
return ((char *) get_mail_conf_str(vstring_str(buf),
defval, min, max));
}
static int dict_ldap_get_dict_int(const char *opt_dict_name,
const char *name, int defval, int min, int max)
{
const char *strval;
int intval;
char junk;
if ((strval = (char *) dict_lookup(opt_dict_name, name)) != 0) {
if (sscanf(strval, "%d%c", &intval, &junk) != 1)
msg_fatal("%s: bad numerical configuration: %s = %s",
opt_dict_name, name, strval);
} else
intval = defval;
if (min && intval < min)
msg_fatal("%s: invalid %s parameter value %d < %d",
opt_dict_name, name, intval, min);
if (max && intval > max)
msg_fatal("%s: invalid %s parameter value %d > %d",
opt_dict_name, name, intval, max);
return (intval);
}
static int dict_ldap_get_mail_int(const char *opt_dict_name,
const char *name, int defval, int min, int max)
{
static VSTRING *buf = 0;
if (buf == 0)
buf = vstring_alloc(15);
vstring_sprintf(buf, "%s_%s", opt_dict_name, name);
return (get_mail_conf_int(vstring_str(buf), defval, min, max));
}
static int dict_ldap_get_dict_bool(const char *opt_dict_name,
const char *name, int defval)
{
const char *strval;
int intval;
if ((strval = (char *) dict_lookup(opt_dict_name, name)) != 0) {
if (strcasecmp(strval, CONFIG_BOOL_YES) == 0) {
intval = 1;
} else if (strcasecmp(strval, CONFIG_BOOL_NO) == 0) {
intval = 0;
} else {
msg_fatal("%s: bad boolean configuration: %s = %s",
opt_dict_name, name, strval);
}
} else
intval = defval;
return (intval);
}
static int dict_ldap_get_mail_bool(const char *opt_dict_name,
const char *name, int defval)
{
static VSTRING *buf = 0;
if (buf == 0)
buf = vstring_alloc(15);
vstring_sprintf(buf, "%s_%s", opt_dict_name, name);
return (get_mail_conf_bool(vstring_str(buf), defval));
}
static int dict_ldap_get_errno(LDAP * ld)
{
int rc;
@@ -1243,6 +1153,7 @@ static void dict_ldap_close(DICT *dict)
}
binhash_delete(conn_hash, ht->key, ht->key_len, myfree);
}
cfg_parser_free(dict_ldap->parser);
myfree(dict_ldap->ldapsource);
myfree(dict_ldap->server_host);
myfree(dict_ldap->search_base);
@@ -1275,7 +1186,6 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
char *s;
char *h;
char *server_host;
CFG_PARSER parser;
char *domainlist;
char *scope;
char *attr;
@@ -1291,43 +1201,22 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
dict_ldap->dict.flags = dict_flags | DICT_FLAG_FIXED;
dict_ldap->ld = NULL;
dict_ldap->parser = cfg_parser_alloc(ldapsource);
dict_ldap->ldapsource = mystrdup(ldapsource);
if (ldapsource[0] == '/' || ldapsource[0] == '.') {
dict_load_file(ldapsource, ldapsource);
parser.get_str = dict_ldap_get_dict_str;
parser.get_int = dict_ldap_get_dict_int;
parser.get_bool = dict_ldap_get_dict_bool;
} else {
/*
* msg_warn("Defining LDAP attributes in main.cf is deprecated. Use
* ldap:/path/to/file instead");
*/
parser.get_str = dict_ldap_get_mail_str;
parser.get_int = dict_ldap_get_mail_int;
parser.get_bool = dict_ldap_get_mail_bool;
}
server_host = parser.get_str(ldapsource, "server_host",
server_host = cfg_get_str(dict_ldap->parser, "server_host",
"localhost", 1, 0);
if (msg_verbose)
msg_info("%s: %s server_host is %s", myname, ldapsource,
server_host);
/*
* get configured value of "server_port"; default to LDAP_PORT (389)
*/
dict_ldap->server_port =
parser.get_int(ldapsource, "server_port", LDAP_PORT, 0, 0);
if (msg_verbose)
msg_info("%s: %s server_port is %d", myname, ldapsource,
dict_ldap->server_port);
cfg_get_int(dict_ldap->parser, "server_port", LDAP_PORT, 0, 0);
/*
* Define LDAP Version.
*/
dict_ldap->version = parser.get_int(ldapsource, "version", 2, 0, 0);
dict_ldap->version = cfg_get_int(dict_ldap->parser, "version", 2, 2, 0);
switch (dict_ldap->version) {
case 2:
dict_ldap->version = LDAP_VERSION2;
@@ -1402,34 +1291,26 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
/*
* Scope handling thanks to Carsten Hoeger of SuSE.
*/
scope = parser.get_str(ldapsource, "scope", "sub", 0, 0);
scope = cfg_get_str(dict_ldap->parser, "scope", "sub", 1, 0);
if (strcasecmp(scope, "one") == 0) {
dict_ldap->scope = LDAP_SCOPE_ONELEVEL;
if (msg_verbose)
msg_info("%s: %s scope is LDAP_SCOPE_ONELEVEL", myname, ldapsource);
} else if (strcasecmp(scope, "base") == 0) {
dict_ldap->scope = LDAP_SCOPE_BASE;
if (msg_verbose)
msg_info("%s: %s scope is LDAP_SCOPE_BASE", myname, ldapsource);
} else {
} else if (strcasecmp(scope, "sub") == 0) {
dict_ldap->scope = LDAP_SCOPE_SUBTREE;
} else {
msg_warn("%s: %s: Unrecognized value %s specified for scope; using sub",
myname, ldapsource, scope);
dict_ldap->scope = LDAP_SCOPE_SUBTREE;
if (msg_verbose)
msg_info("%s: %s scope is LDAP_SCOPE_SUBTREE", myname, ldapsource);
}
myfree(scope);
dict_ldap->search_base = parser.get_str(ldapsource, "search_base",
dict_ldap->search_base = cfg_get_str(dict_ldap->parser, "search_base",
"", 0, 0);
if (msg_verbose)
msg_info("%s: %s search_base is %s", myname, ldapsource,
dict_ldap->search_base);
domainlist = parser.get_str(ldapsource, "domain", "", 0, 0);
domainlist = cfg_get_str(dict_ldap->parser, "domain", "", 0, 0);
if (*domainlist) {
#ifdef MATCH_FLAG_NONE
dict_ldap->domain = match_list_init(MATCH_FLAG_NONE,
@@ -1438,7 +1319,8 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
dict_ldap->domain = match_list_init(domainlist, 1, match_string);
#endif
if (dict_ldap->domain == NULL)
msg_warn("%s: domain match list creation using \"%s\" failed, will continue without it", myname, domainlist);
msg_warn("%s: domain match list creation using \"%s\" failed, will continue without it",
myname, domainlist);
if (msg_verbose)
msg_info("%s: domain list created using \"%s\"", myname,
domainlist);
@@ -1453,40 +1335,28 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
* Thanks to Manuel Guesdon for spotting that this wasn't really getting
* set.
*/
dict_ldap->timeout =
parser.get_int(ldapsource, "timeout", 10, 0, 0);
if (msg_verbose)
msg_info("%s: %s timeout is %d", myname, ldapsource,
dict_ldap->timeout);
dict_ldap->timeout = cfg_get_int(dict_ldap->parser, "timeout",
10, 0, 0);
dict_ldap->query_filter =
parser.get_str(ldapsource, "query_filter",
cfg_get_str(dict_ldap->parser, "query_filter",
"(mailacceptinggeneralid=%s)", 0, 0);
if (msg_verbose)
msg_info("%s: %s query_filter is %s", myname, ldapsource,
dict_ldap->query_filter);
dict_ldap->result_filter =
parser.get_str(ldapsource, "result_filter", "%s", 0, 0);
if (msg_verbose)
msg_info("%s: %s result_filter is %s", myname, ldapsource,
dict_ldap->result_filter);
cfg_get_str(dict_ldap->parser, "result_filter", "%s", 0, 0);
if (strcmp(dict_ldap->result_filter, "%s") == 0) {
myfree(dict_ldap->result_filter);
dict_ldap->result_filter = NULL;
}
attr = parser.get_str(ldapsource, "result_attribute", "maildrop", 0, 0);
if (msg_verbose)
msg_info("%s: %s result_attribute is %s", myname, ldapsource, attr);;
attr = cfg_get_str(dict_ldap->parser, "result_attribute",
"maildrop", 0, 0);
dict_ldap->result_attributes = argv_split(attr, " ,\t\r\n");
dict_ldap->num_attributes = dict_ldap->result_attributes->argc;
myfree(attr);
attr = parser.get_str(ldapsource, "special_result_attribute", "", 0, 0);
if (msg_verbose)
msg_info("%s: %s special_result_attribute is %s", myname, ldapsource,
attr);
attr = cfg_get_str(dict_ldap->parser, "special_result_attribute",
"", 0, 0);
if (*attr) {
argv_split_append(dict_ldap->result_attributes, attr, " ,\t\r\n");
}
@@ -1495,95 +1365,71 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
/*
* get configured value of "bind"; default to true
*/
dict_ldap->bind = parser.get_bool(ldapsource, "bind", 1);
if (msg_verbose)
msg_info("%s: %s bind is %d", myname, ldapsource, dict_ldap->bind);
dict_ldap->bind = cfg_get_bool(dict_ldap->parser, "bind", 1);
/*
* get configured value of "bind_dn"; default to ""
*/
dict_ldap->bind_dn = parser.get_str(ldapsource, "bind_dn", "", 0, 0);
if (msg_verbose)
msg_info("%s: %s bind_dn is %s", myname, ldapsource,
dict_ldap->bind_dn);
dict_ldap->bind_dn = cfg_get_str(dict_ldap->parser, "bind_dn", "", 0, 0);
/*
* get configured value of "bind_pw"; default to ""
*/
dict_ldap->bind_pw = parser.get_str(ldapsource, "bind_pw", "", 0, 0);
if (msg_verbose)
msg_info("%s: %s bind_pw is %s", myname, ldapsource,
dict_ldap->bind_pw);
dict_ldap->bind_pw = cfg_get_str(dict_ldap->parser, "bind_pw", "", 0, 0);
/*
* get configured value of "cache"; default to false
*/
tmp = parser.get_bool(ldapsource, "cache", 0);
tmp = cfg_get_bool(dict_ldap->parser, "cache", 0);
if (tmp)
msg_warn("%s: %s ignoring cache", myname, ldapsource);
/*
* get configured value of "cache_expiry"; default to 30 seconds
*/
tmp = parser.get_int(ldapsource, "cache_expiry", -1, 0, 0);
tmp = cfg_get_int(dict_ldap->parser, "cache_expiry", -1, 0, 0);
if (tmp >= 0)
msg_warn("%s: %s ignoring cache_expiry", myname, ldapsource);
/*
* get configured value of "cache_size"; default to 32k
*/
tmp = parser.get_int(ldapsource, "cache_size", -1, 0, 0);
tmp = cfg_get_int(dict_ldap->parser, "cache_size", -1, 0, 0);
if (tmp >= 0)
msg_warn("%s: %s ignoring cache_size", myname, ldapsource);
/*
* get configured value of "recursion_limit"; default to 1000
*/
dict_ldap->recursion_limit = parser.get_int(ldapsource, "recursion_limit",
1000, 1, 0);
if (msg_verbose)
msg_info("%s: %s recursion_limit is %ld", myname, ldapsource,
dict_ldap->recursion_limit);
dict_ldap->recursion_limit = cfg_get_int(dict_ldap->parser,
"recursion_limit", 1000, 1, 0);
/*
* get configured value of "expansion_limit"; default to 0
*/
dict_ldap->expansion_limit = parser.get_int(ldapsource, "expansion_limit",
0, 0, 0);
if (msg_verbose)
msg_info("%s: %s expansion_limit is %ld", myname, ldapsource,
dict_ldap->expansion_limit);
dict_ldap->expansion_limit = cfg_get_int(dict_ldap->parser,
"expansion_limit", 0, 0, 0);
/*
* get configured value of "size_limit"; default to expansion_limit
*/
dict_ldap->size_limit = parser.get_int(ldapsource, "size_limit",
dict_ldap->size_limit = cfg_get_int(dict_ldap->parser, "size_limit",
dict_ldap->expansion_limit,
0, 0);
if (msg_verbose)
msg_info("%s: %s size_limit is %ld", myname, ldapsource,
dict_ldap->size_limit);
/*
* Alias dereferencing suggested by Mike Mattice.
*/
dict_ldap->dereference = parser.get_int(ldapsource, "dereference", 0, 0,
0);
dict_ldap->dereference = cfg_get_int(dict_ldap->parser, "dereference",
0, 0, 0);
if (dict_ldap->dereference < 0 || dict_ldap->dereference > 3) {
msg_warn("%s: %s Unrecognized value %d specified for dereference; using 0",
myname, ldapsource, dict_ldap->dereference);
dict_ldap->dereference = 0;
}
if (msg_verbose)
msg_info("%s: %s dereference is %d", myname, ldapsource,
dict_ldap->dereference);
/* Referral chasing */
dict_ldap->chase_referrals = parser.get_bool(ldapsource, "chase_referrals",
0);
if (msg_verbose)
msg_info("%s: %s chase_referrals is %d", myname, ldapsource,
dict_ldap->chase_referrals);
dict_ldap->chase_referrals = cfg_get_bool(dict_ldap->parser,
"chase_referrals", 0);
#ifdef LDAP_API_FEATURE_X_OPENLDAP
@@ -1591,65 +1437,47 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
* TLS options
*/
/* get configured value of "start_tls"; default to no */
dict_ldap->start_tls = parser.get_bool(ldapsource, "start_tls", 0);
if (msg_verbose)
msg_info("%s: %s start_tls is %d", myname, ldapsource,
dict_ldap->start_tls);
dict_ldap->start_tls = cfg_get_bool(dict_ldap->parser, "start_tls", 0);
if (dict_ldap->start_tls && dict_ldap->version < LDAP_VERSION3) {
msg_warn("%s: %s start_tls requires protocol version 3", myname, ldapsource);
msg_warn("%s: %s start_tls requires protocol version 3",
myname, ldapsource);
dict_ldap->version = LDAP_VERSION3;
}
/* get configured value of "tls_require_cert"; default to no */
dict_ldap->tls_require_cert = parser.get_bool(ldapsource, "tls_require_cert", 0);
if (msg_verbose)
msg_info("%s: %s tls_require_cert is %d", myname, ldapsource,
dict_ldap->tls_require_cert);
dict_ldap->tls_require_cert = cfg_get_bool(dict_ldap->parser,
"tls_require_cert", 0);
/* get configured value of "tls_ca_cert_file"; default "" */
dict_ldap->tls_ca_cert_file = parser.get_str(ldapsource, "tls_ca_cert_file",
"", 0, 0);
if (msg_verbose)
msg_info("%s: %s tls_ca_cert_file is %s", myname, ldapsource,
dict_ldap->tls_ca_cert_file);
dict_ldap->tls_ca_cert_file = cfg_get_str(dict_ldap->parser,
"tls_ca_cert_file", "", 0, 0);
/* get configured value of "tls_ca_cert_dir"; default "" */
dict_ldap->tls_ca_cert_dir = parser.get_str(ldapsource, "tls_ca_cert_dir",
"", 0, 0);
if (msg_verbose)
msg_info("%s: %s tls_ca_cert_dir is %s", myname, ldapsource,
dict_ldap->tls_ca_cert_dir);
dict_ldap->tls_ca_cert_dir = cfg_get_str(dict_ldap->parser,
"tls_ca_cert_dir", "", 0, 0);
/* get configured value of "tls_cert"; default "" */
dict_ldap->tls_cert = parser.get_str(ldapsource, "tls_cert",
dict_ldap->tls_cert = cfg_get_str(dict_ldap->parser, "tls_cert",
"", 0, 0);
if (msg_verbose)
msg_info("%s: %s tls_cert is %s", myname, ldapsource,
dict_ldap->tls_cert);
/* get configured value of "tls_key"; default "" */
dict_ldap->tls_key = parser.get_str(ldapsource, "tls_key",
dict_ldap->tls_key = cfg_get_str(dict_ldap->parser, "tls_key",
"", 0, 0);
if (msg_verbose)
msg_info("%s: %s tls_key is %s", myname, ldapsource,
dict_ldap->tls_key);
/* get configured value of "tls_random_file"; default "" */
dict_ldap->tls_random_file = parser.get_str(ldapsource,
dict_ldap->tls_random_file = cfg_get_str(dict_ldap->parser,
"tls_random_file", "", 0, 0);
if (msg_verbose)
msg_info("%s: %s tls_random_file is %s", myname, ldapsource,
dict_ldap->tls_random_file);
/* get configured value of "tls_cipher_suite"; default "" */
dict_ldap->tls_cipher_suite = parser.get_str(ldapsource,
dict_ldap->tls_cipher_suite = cfg_get_str(dict_ldap->parser,
"tls_cipher_suite", "", 0, 0);
if (msg_verbose)
msg_info("%s: %s tls_cipher_suite is %s", myname, ldapsource,
dict_ldap->tls_cipher_suite);
#endif
/*
* Debug level.
*/
#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN)
dict_ldap->debuglevel = parser.get_int(ldapsource, "debuglevel", 0, 0, 0);
if (msg_verbose)
msg_info("%s: %s debuglevel is %d", myname, ldapsource,
dict_ldap->debuglevel);
dict_ldap->debuglevel = cfg_get_int(dict_ldap->parser, "debuglevel",
0, 0, 0);
#endif
/*

View File

@@ -2,9 +2,8 @@
/* NAME
/* dict_mysql 3
/* SUMMARY
/* dictionary manager interface to db files
/* dictionary manager interface to MySQL databases
/* SYNOPSIS
/* #include <dict.h>
/* #include <dict_mysql.h>
/*
/* DICT *dict_mysql_open(name, open_flags, dict_flags)
@@ -24,25 +23,21 @@
/* 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 mysql server.
/*
/* .PP
/* Arguments:
/* .IP name
/* The path of the MySQL configuration file. The file encodes a 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 "vmailer_info" located on hosts host1.some.domain and
/* host2.some.domain, logging in as user "vmailer" and password "passwd" then
/* the configuration file should read:
/* Either the path to the MySQL configuration file (if it starts
/* with '/' or '.'), or the prefix which will be used to obtain
/* main.cf configuration parameters for this search.
/*
/* user = vmailer
/* password = passwd
/* DBname = vmailer_info
/* table = aliases
/* select_field = forw_addr
/* where_field = alias
/* hosts = host1.some.domain host2.some.domain
/* In the first case, the configuration parameters below are
/* specified in the file as \fIname\fR=\fBvalue\fR pairs.
/*
/* In the second case, the configuration parameters are
/* prefixed with the value of \fIname\fR and an underscore,
/* and they are specified in main.cf. For example, if this
/* value is \fImysqlsource\fR, the parameters would look like
/* \fImysqlsource_user\fR, \fImysqlsource_table\fR, and so on.
/*
/* .IP other_name
/* reference for outside use.
@@ -50,6 +45,51 @@
/* Must be O_RDONLY.
/* .IP dict_flags
/* See dict_open(3).
/* .PP
/* Configuration parameters:
/*
/* The parameters encodes a number of pieces of information:
/* username, password, databasename, table, select_field,
/* where_field, and hosts:
/* .IP \fIuser\fR
/* Username for connecting to the database.
/* .IP \fIpassword\fR
/* Password for the above.
/* .IP \fIdbname\fR
/* Name of the database.
/* .IP \fItable\fR
/* Name of the table.
/* .IP \fIselect_field\fR
/* Name of the result field.
/* .IP \fIwhere_field\fR
/* Field used in the WHERE clause.
/* .IP \fIadditional_conditions\fR
/* Additional conditions to the WHERE clause.
/* .IP \fIhosts\fR
/* List of hosts to connect to.
/* .PP
/* 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 "vmailer_info" located on hosts
/* host1.some.domain and host2.some.domain, logging in as user
/* "vmailer" and password "passwd" then the configuration file
/* should read:
/* .PP
/* \fIuser\fR = \fBvmailer\fR
/* .br
/* \fIpassword\fR = \fBpasswd\fR
/* .br
/* \fIdbname\fR = \fBvmailer_info\fR
/* .br
/* \fItable\fR = \fBaliases\fR
/* .br
/* \fIselect_field\fR = \fBforw_addr\fR
/* .br
/* \fIwhere_field\fR = \fBalias\fR
/* .br
/* \fIhosts\fR = \fBhost1.some.domain\fR \fBhost2.some.domain\fR
/* .PP
/* SEE ALSO
/* dict(3) generic dictionary manager
/* AUTHOR(S)
@@ -78,15 +118,23 @@
#include <mysql.h>
/* Utility library. */
#include "dict.h"
#include "msg.h"
#include "mymalloc.h"
#include "dict_mysql.h"
#include "argv.h"
#include "vstring.h"
#include "split_at.h"
#include "find_inet.h"
/* Global library. */
#include "cfg_parser.h"
/* Application-specific. */
#include "dict_mysql.h"
/* need some structs to help organize things */
typedef struct {
MYSQL *db;
@@ -103,6 +151,7 @@ typedef struct {
} PLMYSQL;
typedef struct {
CFG_PARSER *parser;
char *username;
char *password;
char *dbname;
@@ -132,7 +181,6 @@ static void plmysql_dealloc(PLMYSQL *);
static void plmysql_close_host(HOST *);
static void plmysql_down_host(HOST *);
static void plmysql_connect_single(HOST *, char *, char *, char *);
static int plmysql_ready_reconn(HOST *);
static const char *dict_mysql_lookup(DICT *, const char *);
DICT *dict_mysql_open(const char *, int, int);
static void dict_mysql_close(DICT *);
@@ -363,7 +411,6 @@ static void plmysql_down_host(HOST *host)
DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags)
{
DICT_MYSQL *dict_mysql;
int connections;
/*
* Sanity checks.
@@ -386,92 +433,50 @@ DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags)
}
/* mysqlname_parse - parse mysql configuration file */
static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path)
static MYSQL_NAME *mysqlname_parse(const char *mysqlcf)
{
const char *myname = "mysqlname_parse";
int i;
char *nameval;
char *hosts;
MYSQL_NAME *name = (MYSQL_NAME *) mymalloc(sizeof(MYSQL_NAME));
ARGV *hosts_argv;
VSTRING *opt_dict_name;
/*
* setup a dict containing info in the mysql 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
* mysql maps, which can be confusing.
*/
opt_dict_name = vstring_alloc(64);
vstring_sprintf(opt_dict_name, "mysql opt dict %s", mysqlcf_path);
dict_load_file(vstring_str(opt_dict_name), mysqlcf_path);
/* mysql 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("mysqlname_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("mysqlname_parse(): set password to '%s'", name->password);
/* parser */
name->parser = cfg_parser_alloc(mysqlcf);
/* database name lookup */
if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "dbname")) == NULL)
msg_fatal("%s: mysql options file does not include database name", mysqlcf_path);
else
name->dbname = mystrdup(nameval);
if (msg_verbose)
msg_info("mysqlname_parse(): set database name to '%s'", name->dbname);
/* username */
name->username = cfg_get_str(name->parser, "user", "", 0, 0);
/* table lookup */
if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "table")) == NULL)
msg_fatal("%s: mysql options file does not include table name", mysqlcf_path);
else
name->table = mystrdup(nameval);
if (msg_verbose)
msg_info("mysqlname_parse(): set table name to '%s'", name->table);
/* password */
name->password = cfg_get_str(name->parser, "password", "", 0, 0);
/* select field lookup */
if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "select_field")) == NULL)
msg_fatal("%s: mysql options file does not include select field", mysqlcf_path);
else
name->select_field = mystrdup(nameval);
if (msg_verbose)
msg_info("mysqlname_parse(): set select_field to '%s'", name->select_field);
/* database name */
name->dbname = cfg_get_str(name->parser, "dbname", "", 1, 0);
/* where field lookup */
if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "where_field")) == NULL)
msg_fatal("%s: mysql options file does not include where field", mysqlcf_path);
else
name->where_field = mystrdup(nameval);
if (msg_verbose)
msg_info("mysqlname_parse(): set where_field to '%s'", name->where_field);
/* table name */
name->table = cfg_get_str(name->parser, "table", "", 1, 0);
/* select field */
name->select_field = cfg_get_str(name->parser, "select_field", "", 1, 0);
/* where field */
name->where_field = cfg_get_str(name->parser, "where_field", "", 1, 0);
/* 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("mysqlname_parse(): set additional_conditions to '%s'", name->additional_conditions);
name->additional_conditions = cfg_get_str(name->parser,
"additional_conditions",
"", 0, 0);
/* mysql server hosts */
if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "hosts")) == NULL)
hosts = mystrdup("");
else
hosts = mystrdup(nameval);
hosts = cfg_get_str(name->parser, "hosts", "", 0, 0);
/* 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("mysqlname_parse(): no hostnames specified, defaulting to 'localhost'");
msg_info("%s: %s: no hostnames specified, defaulting to 'localhost'",
myname, mysqlcf);
argv_add(hosts_argv, "localhost", ARGV_END);
argv_terminate(hosts_argv);
}
@@ -481,11 +486,10 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path)
for (i = 0; hosts_argv->argv[i] != NULL; i++) {
name->hostnames[i] = mystrdup(hosts_argv->argv[i]);
if (msg_verbose)
msg_info("mysqlname_parse(): adding host '%s' to list of mysql server hosts",
name->hostnames[i]);
msg_info("%s: %s: adding host '%s' to list of mysql server hosts",
myname, mysqlcf, name->hostnames[i]);
}
myfree(hosts);
vstring_free(opt_dict_name);
argv_free(hosts_argv);
return name;
}
@@ -498,9 +502,7 @@ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf_path)
static PLMYSQL *plmysql_init(char *hostnames[], int len_hosts)
{
PLMYSQL *PLDB;
MYSQL *dbs;
int i;
HOST host;
if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == NULL) {
msg_fatal("mymalloc of pldb failed");
@@ -537,6 +539,7 @@ static void dict_mysql_close(DICT *dict)
DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
plmysql_dealloc(dict_mysql->pldb);
cfg_parser_free(dict_mysql->name->parser);
myfree(dict_mysql->name->username);
myfree(dict_mysql->name->password);
myfree(dict_mysql->name->dbname);

View File

@@ -2,7 +2,7 @@
/* NAME
/* dict_pgsql 3
/* SUMMARY
/* dictionary manager interface to Postgresql files
/* dictionary manager interface to PostgreSQL databases
/* SYNOPSIS
/* #include <dict_pgsql.h>
/*
@@ -24,12 +24,61 @@
/* 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.
/*
/* .PP
/* 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.
/* Either the path to the PostgreSQL configuration file (if it
/* starts with '/' or '.'), or the prefix which will be used to
/* obtain main.cf configuration parameters for this search.
/*
/* In the first case, the configuration parameters below are
/* specified in the file as \fIname\fR=\fBvalue\fR pairs.
/*
/* In the second case, the configuration parameters are
/* prefixed with the value of \fIname\fR and an underscore,
/* and they are specified in main.cf. For example, if this
/* value is \fIpgsqlsource\fR, the parameters would look like
/* \fIpgsqlsource_user\fR, \fIpgsqlsource_table\fR, and so on.
/* .IP other_name
/* reference for outside use.
/* .IP open_flags
/* Must be O_RDONLY.
/* .IP dict_flags
/* See dict_open(3).
/*
/* .PP
/* Configuration parameters:
/*
/* The parameters encode a number of pieces of information:
/* username, password, databasename, table, select_field,
/* where_field, and hosts:
/* .IP \fIuser\fR
/* Username for connecting to the database.
/* .IP \fIpassword\fR
/* Password for the above.
/* .IP \fIdbname\fR
/* Name of the database.
/* .IP \fItable\fR
/* Name of the table.
/* .IP \fIselect_field\fR
/* Name of the result field.
/* .IP \fIwhere_field\fR
/* Field used in the WHERE clause.
/* .IP \fIadditional_conditions\fR
/* Additional conditions to the WHERE clause.
/* .IP \fIquery\fR
/* Query overriding \fItable\fR, \fIselect_field\fR,
/* \fIwhere_field\fR, and \fIadditional_conditions\fR. Before the
/* query is actually issued, all occurrences of %s are replaced
/* with the address to look up, %u are replaced with the user
/* portion, and %d with the domain portion.
/* .IP \fIselect_function\fR
/* Function to be used instead of the SELECT statement. Overrides
/* both \fIquery\fR and \fItable\fR, \fIselect_field\fR,
/* \fIwhere_field\fR, and \fIadditional_conditions\fR settings.
/* .IP \fIhosts\fR
/* List of hosts to connect to.
/* .PP
/* 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>'
@@ -37,21 +86,21 @@
/* 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 open_flags
/* Must be O_RDONLY.
/* .IP dict_flags
/* See dict_open(3).
/* .PP
/* \fIuser\fR = \fBpostfix\fR
/* .br
/* \fIpassword\fR = \fBpasswd\fR
/* .br
/* \fIdbname\fR = \fBpostfix_info\fR
/* .br
/* \fItable\fR = \fBaliases\fR
/* .br
/* \fIselect_field\fR = \fBforw_addr\fR
/* .br
/* \fIwhere_field\fR = \fBalias\fR
/* .br
/* \fIhosts\fR = \fBhost1.some.domain\fR \fBhost2.some.domain\fR
/* .PP
/* SEE ALSO
/* dict(3) generic dictionary manager
/* AUTHOR(S)
@@ -70,6 +119,7 @@
/*--*/
/* System library. */
#include "sys_defs.h"
#ifdef HAS_PGSQL
@@ -87,15 +137,23 @@
#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"
/* Global library. */
#include "cfg_parser.h"
/* Application-specific. */
#include "dict_pgsql.h"
#define STATACTIVE 0
#define STATFAIL 1
#define STATUNTRIED 2
@@ -114,6 +172,7 @@ typedef struct {
} PLPGSQL;
typedef struct {
CFG_PARSER *parser;
char *username;
char *password;
char *dbname;
@@ -202,7 +261,7 @@ static void pgsql_escape_string(char *new, const char *old, unsigned int len)
*/
static void dict_pgsql_expand_filter(char *filter, char *value, VSTRING *out)
{
char *myname = "dict_pgsql_expand_filter";
const char *myname = "dict_pgsql_expand_filter";
char *sub,
*end;
@@ -285,7 +344,8 @@ static const char *dict_pgsql_lookup(DICT *dict, const char *name)
} 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,
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,
@@ -516,126 +576,71 @@ DICT *dict_pgsql_open(const char *name, int open_flags, int dict_flags)
}
/* pgsqlname_parse - parse pgsql configuration file */
static PGSQL_NAME *pgsqlname_parse(const char *pgsqlcf_path)
static PGSQL_NAME *pgsqlname_parse(const char *pgsqlcf)
{
const char *myname = "pgsqlname_parse";
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);
name->parser = cfg_parser_alloc(pgsqlcf);
/* username */
name->username = cfg_get_str(name->parser, "user", "", 0, 0);
/* password */
name->password = cfg_get_str(name->parser, "password", "", 0, 0);
/* 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;
name->dbname = cfg_get_str(name->parser, "dbname", "", 1, 0);
/*
* 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) {
name->select_function = cfg_get_str(name->parser, "select_function",
NULL, 0, 0);
name->query = cfg_get_str(name->parser, "query", NULL, 0, 0);
/* 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 {
if (name->select_function == 0 && name->query == 0) {
/*
* We have an old style 'select %s from %s...' call, so get the
* fields
* We have an old style 'select %s from %s...' call
*/
/* 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);
/* table name */
name->table = cfg_get_str(name->parser, "table", "", 1, 0);
/* 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);
/* select field */
name->select_field = cfg_get_str(name->parser, "select_field",
"", 1, 0);
/* 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);
/* where field */
name->where_field = cfg_get_str(name->parser, "where_field",
"", 1, 0);
/* 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);
name->additional_conditions = cfg_get_str(name->parser,
"additional_conditions",
"", 0, 0);
} else {
name->table = 0;
name->select_field = 0;
name->where_field = 0;
name->additional_conditions = 0;
}
/* pgsql server hosts */
if ((nameval = (char *) dict_lookup(vstring_str(opt_dict_name), "hosts")) == NULL)
hosts = mystrdup("");
else
hosts = mystrdup(nameval);
/* server hosts */
hosts = cfg_get_str(name->parser, "hosts", "", 0, 0);
/* 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'");
msg_info("%s: %s: no hostnames specified, defaulting to 'localhost'",
myname, pgsqlcf);
argv_add(hosts_argv, "localhost", ARGV_END);
argv_terminate(hosts_argv);
}
@@ -645,11 +650,10 @@ static PGSQL_NAME *pgsqlname_parse(const char *pgsqlcf_path)
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]);
msg_info("%s: %s: adding host '%s' to list of pgsql server hosts",
myname, pgsqlcf, name->hostnames[i]);
}
myfree(hosts);
vstring_free(opt_dict_name);
argv_free(hosts_argv);
return name;
}
@@ -699,12 +703,21 @@ static void dict_pgsql_close(DICT *dict)
DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
plpgsql_dealloc(dict_pgsql->pldb);
cfg_parser_free(dict_pgsql->name->parser);
myfree(dict_pgsql->name->username);
myfree(dict_pgsql->name->password);
myfree(dict_pgsql->name->dbname);
if (dict_pgsql->name->table)
myfree(dict_pgsql->name->table);
if (dict_pgsql->name->query)
myfree(dict_pgsql->name->query);
if (dict_pgsql->name->select_function)
myfree(dict_pgsql->name->select_function);
if (dict_pgsql->name->select_field)
myfree(dict_pgsql->name->select_field);
if (dict_pgsql->name->where_field)
myfree(dict_pgsql->name->where_field);
if (dict_pgsql->name->additional_conditions)
myfree(dict_pgsql->name->additional_conditions);
for (i = 0; i < dict_pgsql->name->len_hosts; i++) {
myfree(dict_pgsql->name->hostnames[i]);

View File

@@ -33,6 +33,9 @@
/* Global library. */
#include <dict_proxy.h>
#include <dict_ldap.h>
#include <dict_mysql.h>
#include <dict_pgsql.h>
#include <mail_dict.h>
typedef struct {
@@ -42,7 +45,15 @@ typedef struct {
static DICT_OPEN_INFO dict_open_info[] = {
DICT_TYPE_PROXY, dict_proxy_open,
/* XXX LDAP and MYSQL etc. should go here, too. */
#ifdef HAS_LDAP
DICT_TYPE_LDAP, dict_ldap_open,
#endif
#ifdef HAS_MYSQL
DICT_TYPE_MYSQL, dict_mysql_open,
#endif
#ifdef HAS_PGSQL
DICT_TYPE_PGSQL, dict_pgsql_open,
#endif
0,
};

View File

@@ -445,7 +445,6 @@ void mail_params_init()
VAR_MYDEST, DEF_MYDEST, &var_mydest, 0, 0,
VAR_MYORIGIN, DEF_MYORIGIN, &var_myorigin, 1, 0,
VAR_RELAYHOST, DEF_RELAYHOST, &var_relayhost, 0, 0,
VAR_PROGRAM_DIR, DEF_PROGRAM_DIR, &var_program_dir, 1, 0,
VAR_DAEMON_DIR, DEF_DAEMON_DIR, &var_daemon_dir, 1, 0,
VAR_COMMAND_DIR, DEF_COMMAND_DIR, &var_command_dir, 1, 0,
VAR_QUEUE_DIR, DEF_QUEUE_DIR, &var_queue_dir, 1, 0,

View File

@@ -87,7 +87,7 @@ extern char *var_myorigin;
* mail to other destinations.
*/
#define VAR_MYDEST "mydestination"
#define DEF_MYDEST "$myhostname, localhost.$mydomain localhost"
#define DEF_MYDEST "$myhostname, localhost.$mydomain, localhost"
extern char *var_mydest;
/*
@@ -198,13 +198,8 @@ extern int var_smtp_mxsess_limit;
extern char *var_queue_dir;
/*
* Location of daemon programs.
* Location of command and daemon programs.
*/
#define VAR_PROGRAM_DIR "program_directory"
#ifndef DEF_PROGRAM_DIR
#define DEF_PROGRAM_DIR "/usr/libexec/postfix"
#endif
#define VAR_DAEMON_DIR "daemon_directory"
#ifndef DEF_DAEMON_DIR
#define DEF_DAEMON_DIR "/usr/libexec/postfix"
@@ -298,8 +293,8 @@ extern char *var_rcpt_witheld;
extern bool var_strict_rfc821_env;
/*
* Standards violation: send "250 AUTH=list" in order to accomodate broken
* Microsoft clients.
* Standards violation: send "250 AUTH=list" in order to accomodate clients
* that implement an old version of the protocol.
*/
#define VAR_BROKEN_AUTH_CLNTS "broken_sasl_auth_clients"
#define DEF_BROKEN_AUTH_CLNTS 0
@@ -549,7 +544,7 @@ extern int var_max_backoff_time;
extern int var_max_queue_time;
#define VAR_DSN_QUEUE_TIME "bounce_queue_lifetime"
#define DEF_DSN_QUEUE_TIME "$" VAR_MAX_QUEUE_TIME
#define DEF_DSN_QUEUE_TIME "$" VAR_MAX_QUEUE_TIME /* XXX no time unit */
extern int var_dsn_queue_time;
#define VAR_DELAY_WARN_TIME "delay_warning_time"
@@ -913,6 +908,10 @@ extern bool var_smtpd_sasl_enable;
#define DEF_SMTPD_SASL_OPTS "noanonymous"
extern char *var_smtpd_sasl_opts;
#define VAR_SMTPD_SASL_APPNAME "smtpd_sasl_application_name"
#define DEF_SMTPD_SASL_APPNAME "smtpd"
extern char *var_smtpd_sasl_appname;
#define VAR_SMTPD_SASL_REALM "smtpd_sasl_local_domain"
#define DEF_SMTPD_SASL_REALM ""
extern char *var_smtpd_sasl_realm;
@@ -1077,8 +1076,8 @@ extern int var_lmtp_quit_tmout;
extern bool var_lmtp_send_xforward;
/*
* Cleanup service. Header info that exceeds $header_size_limit bytes forces
* the start of the message body.
* Cleanup service. Header info that exceeds $header_size_limit bytes or
* $header_address_token_limit tokens is discarded.
*/
#define VAR_HOPCOUNT_LIMIT "hopcount_limit"
#define DEF_HOPCOUNT_LIMIT 50

View File

@@ -114,13 +114,7 @@ static VSTRING *id_buf;
void mail_stream_cleanup(MAIL_STREAM *info)
{
int status;
if (info->stream) {
if ((status = info->close(info->stream)) != 0)
msg_warn("bad mail stream close status %d", status);
info->stream = 0;
}
FREE_AND_WIPE(info->close, info->stream);
FREE_AND_WIPE(myfree, info->queue);
FREE_AND_WIPE(myfree, info->id);
FREE_AND_WIPE(myfree, info->class);

View File

@@ -20,7 +20,7 @@
* 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 "20040101"
#define MAIL_RELEASE_DATE "20040104"
#define VAR_MAIL_VERSION "mail_version"
#define DEF_MAIL_VERSION "2.0.16-" MAIL_RELEASE_DATE

View File

@@ -1,11 +1,9 @@
SHELL = /bin/sh
SRCS = qmgr.c qmgr_active.c qmgr_transport.c qmgr_queue.c qmgr_entry.c \
qmgr_message.c qmgr_deliver.c qmgr_move.c qmgr_rcpt_list.c \
qmgr_job.c qmgr_peer.c \
qmgr_defer.c qmgr_enable.c qmgr_scan.c qmgr_bounce.c
OBJS = qmgr.o qmgr_active.o qmgr_transport.o qmgr_queue.o qmgr_entry.o \
qmgr_message.o qmgr_deliver.o qmgr_move.o qmgr_rcpt_list.o \
qmgr_job.o qmgr_peer.o \
qmgr_defer.o qmgr_enable.o qmgr_scan.o qmgr_bounce.o
HDRS = qmgr.h
TESTSRC =
@@ -28,10 +26,10 @@ test: $(TESTPROG)
tests: test
update: ../../libexec/n$(PROG)
update: ../../libexec/o$(PROG)
../../libexec/n$(PROG): $(PROG)
cp $(PROG) ../../libexec/n$(PROG)
../../libexec/o$(PROG): $(PROG)
cp $(PROG) ../../libexec/o$(PROG)
printfck: $(OBJS) $(PROG)
rm -rf printfck
@@ -158,16 +156,6 @@ qmgr_entry.o: ../../include/vbuf.h
qmgr_entry.o: ../../include/mail_params.h
qmgr_entry.o: qmgr.h
qmgr_entry.o: ../../include/scan_dir.h
qmgr_job.o: qmgr_job.c
qmgr_job.o: ../../include/sys_defs.h
qmgr_job.o: ../../include/msg.h
qmgr_job.o: ../../include/htable.h
qmgr_job.o: ../../include/mymalloc.h
qmgr_job.o: ../../include/sane_time.h
qmgr_job.o: qmgr.h
qmgr_job.o: ../../include/vstream.h
qmgr_job.o: ../../include/vbuf.h
qmgr_job.o: ../../include/scan_dir.h
qmgr_message.o: qmgr_message.c
qmgr_message.o: ../../include/sys_defs.h
qmgr_message.o: ../../include/msg.h
@@ -180,7 +168,6 @@ qmgr_message.o: ../../include/valid_hostname.h
qmgr_message.o: ../../include/argv.h
qmgr_message.o: ../../include/stringops.h
qmgr_message.o: ../../include/myflock.h
qmgr_message.o: ../../include/sane_time.h
qmgr_message.o: ../../include/dict.h
qmgr_message.o: ../../include/mail_queue.h
qmgr_message.o: ../../include/mail_params.h
@@ -212,15 +199,6 @@ qmgr_move.o: ../../include/vbuf.h
qmgr_move.o: ../../include/vstream.h
qmgr_move.o: ../../include/mail_scan_dir.h
qmgr_move.o: qmgr.h
qmgr_peer.o: qmgr_peer.c
qmgr_peer.o: ../../include/sys_defs.h
qmgr_peer.o: ../../include/msg.h
qmgr_peer.o: ../../include/htable.h
qmgr_peer.o: ../../include/mymalloc.h
qmgr_peer.o: qmgr.h
qmgr_peer.o: ../../include/vstream.h
qmgr_peer.o: ../../include/vbuf.h
qmgr_peer.o: ../../include/scan_dir.h
qmgr_queue.o: qmgr_queue.c
qmgr_queue.o: ../../include/sys_defs.h
qmgr_queue.o: ../../include/msg.h

View File

@@ -2,7 +2,7 @@
/* NAME
/* qmgr 8
/* SUMMARY
/* Postfix queue manager
/* old Postfix queue manager
/* SYNOPSIS
/* \fBqmgr\fR [generic Postfix daemon options]
/* DESCRIPTION
@@ -80,10 +80,6 @@
/* .IP "\fBdestination status cache\fR"
/* The queue manager avoids unnecessary delivery attempts by
/* maintaining a short-term, in-memory list of unreachable destinations.
/* .IP "\fBpreemptive message scheduling\fR"
/* The queue manager attempts to minimize the average per-recipient delay
/* while still preserving the correct per-message delays, using
/* a sophisticated preemptive message scheduling.
/* TRIGGERS
/* .ad
/* .fi
@@ -155,8 +151,6 @@
/* .SH "Active queue controls"
/* .ad
/* .fi
/* In the text below, \fItransport\fR is the first field in a
/* \fBmaster.cf\fR entry.
/* .IP \fBqmgr_clog_warn_time\fR
/* Minimal delay between warnings that a specific destination
/* is clogging up the active queue. Specify 0 to disable.
@@ -167,19 +161,6 @@
/* .sp
/* This parameter also limits the size of the short-term, in-memory
/* destination cache.
/* .IP \fBqmgr_message_recipient_minimum\fR
/* Per message minimum of in-memory recipients.
/* .IP \fBdefault_recipient_limit\fR
/* Default limit on the number of in-memory recipients per transport.
/* .IP \fItransport\fB_recipient_limit\fR
/* Limit on the number of in-memory recipients, for the named
/* message \fItransport\fR.
/* .IP \fBdefault_extra_recipient_limit\fR
/* Default limit on the total number of per transport in-memory
/* recipients that the preempting messages can have.
/* .IP \fItransport\fB_extra_recipient_limit\fR
/* Limit on the number of in-memory recipients which all preempting
/* messages delivered by the transport \fItransport\fR can have.
/* .SH "Timing controls"
/* .ad
/* .fi
@@ -207,6 +188,19 @@
/* .SH "Concurrency controls"
/* .ad
/* .fi
/* In the text below, \fItransport\fR is the first field in a
/* \fBmaster.cf\fR entry.
/* .IP "\fBqmgr_fudge_factor\fR (valid range: 10..100)"
/* The percentage of delivery resources that a busy mail system will
/* use up for delivery of a large mailing list message.
/* With 100%, delivery of one message does not begin before the previous
/* message has been delivered. This results in good performance for large
/* mailing lists, but results in poor response time for one-to-one mail.
/* With less than 100%, response time for one-to-one mail improves,
/* but large mailing list delivery performance suffers. In the worst
/* case, recipients near the beginning of a large list receive a burst
/* of messages immediately, while recipients near the end of that list
/* receive that same burst of messages a whole day later.
/* .IP \fBinitial_destination_concurrency\fR
/* Initial per-destination concurrency level for parallel delivery
/* to the same destination.
@@ -224,40 +218,6 @@
/* .IP \fItransport\fB_destination_recipient_limit\fR
/* Limit on the number of recipients per message transfer, for the
/* named message \fItransport\fR.
/* .SH "Message scheduling"
/* .ad
/* .fi
/* .IP "\fItransport\fB_delivery_slot_cost\fR (valid range: 0,2,3...)
/* This parameter basically controls how often a message
/* delivered by \fItransport\fR can be preempted by another
/* message.
/* An internal per-message/transport counter is incremented by one
/* for each \fItransport\fB_delivery_slot_cost\fR
/* deliveries handled by \fItransport\fR. This counter represents
/* the number of "available delivery slots" for use by other messages.
/* Current message can be preempted by another message when that
/* other message can be delivered using less \fItransport\fR agents
/* than the value of the "available delivery slots" counter.
/* .sp
/* Value equal to 0 disables the message preemption for \fItransport\fR.
/* .IP \fItransport\fB_minimum_delivery_slots\fR
/* Message preemption is not attempted at all whenever a message
/* that can't ever accumulate at least \fItransport\fB_minimum_delivery_slots\fR
/* available delivery slots is being delivered by \fItransport\fR.
/* .IP "\fItransport\fB_delivery_slot_discount\fR (valid range: 0..100)"
/* .IP \fItransport\fB_delivery_slot_loan\fR
/* These parameters speed up the moment when a message preemption can happen.
/* Instead of waiting until the full amount of delivery slots
/* required is available, the preemption can happen when
/* \fItransport\fB_delivery_slot_discount\fR percent of the required
/* amount plus \fItransport\fB_delivery_slot_loan\fR still remains to
/* be accumulated. Note that the full amount will still have to be
/* accumulated before another preemption can take place later.
/* .IP \fBdefault_delivery_slot_cost\fR
/* .IP \fBdefault_minimum_delivery_slots\fR
/* .IP \fBdefault_delivery_slot_discount\fR
/* .IP \fBdefault_delivery_slot_loan\fR
/* Default values for the transport specific parameters described above.
/* SEE ALSO
/* master(8), process manager
/* syslogd(8) system logging
@@ -271,11 +231,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -320,21 +275,15 @@ int var_max_queue_time;
int var_dsn_queue_time;
int var_qmgr_active_limit;
int var_qmgr_rcpt_limit;
int var_qmgr_msg_rcpt_limit;
int var_xport_rcpt_limit;
int var_stack_rcpt_limit;
int var_delivery_slot_cost;
int var_delivery_slot_loan;
int var_delivery_slot_discount;
int var_min_delivery_slots;
int var_init_dest_concurrency;
int var_transport_retry_time;
int var_dest_con_limit;
int var_dest_rcpt_limit;
char *var_defer_xports;
bool var_allow_min_user;
int var_local_con_lim;
int var_local_rcpt_lim;
int var_qmgr_fudge;
int var_local_rcpt_lim; /* XXX */
int var_local_con_lim; /* XXX */
int var_proc_limit;
bool var_verp_bounce_off;
bool var_sender_routing;
@@ -443,13 +392,16 @@ static int qmgr_loop(char *unused_name, char **unused_argv)
/*
* Let some new blood into the active queue when the queue size is
* smaller than some configurable limit. When the system is under heavy
* load, favor new mail over old mail.
* smaller than some configurable limit, and when the number of in-core
* recipients does not exceed some configurable limit. When the system is
* under heavy load, favor new mail over old mail.
*/
if (qmgr_message_count < var_qmgr_active_limit)
if (qmgr_message_count < var_qmgr_active_limit
&& qmgr_recipient_count < var_qmgr_rcpt_limit)
if ((in_path = qmgr_scan_next(qmgr_incoming)) != 0)
in_feed = qmgr_active_feed(qmgr_incoming, in_path);
if (qmgr_message_count < var_qmgr_active_limit)
if (qmgr_message_count < var_qmgr_active_limit
&& qmgr_recipient_count < var_qmgr_rcpt_limit)
if ((df_path = qmgr_scan_next(qmgr_deferred)) != 0)
qmgr_active_feed(qmgr_deferred, df_path);
@@ -541,16 +493,10 @@ int main(int argc, char **argv)
static CONFIG_INT_TABLE int_table[] = {
VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0,
VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0,
VAR_QMGR_MSG_RCPT_LIMIT, DEF_QMGR_MSG_RCPT_LIMIT, &var_qmgr_msg_rcpt_limit, 1, 0,
VAR_XPORT_RCPT_LIMIT, DEF_XPORT_RCPT_LIMIT, &var_xport_rcpt_limit, 0, 0,
VAR_STACK_RCPT_LIMIT, DEF_STACK_RCPT_LIMIT, &var_stack_rcpt_limit, 0, 0,
VAR_DELIVERY_SLOT_COST, DEF_DELIVERY_SLOT_COST, &var_delivery_slot_cost, 0, 0,
VAR_DELIVERY_SLOT_LOAN, DEF_DELIVERY_SLOT_LOAN, &var_delivery_slot_loan, 0, 0,
VAR_DELIVERY_SLOT_DISCOUNT, DEF_DELIVERY_SLOT_DISCOUNT, &var_delivery_slot_discount, 0, 100,
VAR_MIN_DELIVERY_SLOTS, DEF_MIN_DELIVERY_SLOTS, &var_min_delivery_slots, 0, 0,
VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0,
VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0,
VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0,
VAR_QMGR_FUDGE, DEF_QMGR_FUDGE, &var_qmgr_fudge, 10, 100,
VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0,
VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0,
VAR_PROC_LIMIT, DEF_PROC_LIMIT, &var_proc_limit, 1, 0,

View File

@@ -22,13 +22,9 @@ typedef struct QMGR_TRANSPORT QMGR_TRANSPORT;
typedef struct QMGR_QUEUE QMGR_QUEUE;
typedef struct QMGR_ENTRY QMGR_ENTRY;
typedef struct QMGR_MESSAGE QMGR_MESSAGE;
typedef struct QMGR_JOB QMGR_JOB;
typedef struct QMGR_PEER QMGR_PEER;
typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST;
typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST;
typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST;
typedef struct QMGR_JOB_LIST QMGR_JOB_LIST;
typedef struct QMGR_PEER_LIST QMGR_PEER_LIST;
typedef struct QMGR_RCPT QMGR_RCPT;
typedef struct QMGR_RCPT_LIST QMGR_RCPT_LIST;
typedef struct QMGR_SCAN QMGR_SCAN;
@@ -36,16 +32,17 @@ typedef struct QMGR_SCAN QMGR_SCAN;
/*
* Hairy macros to update doubly-linked lists.
*/
#define QMGR_LIST_ROTATE(head, object, peers) { \
#define QMGR_LIST_ROTATE(head, object) { \
head.next->peers.prev = head.prev; \
head.prev->peers.next = head.next; \
head.next = object->peers.next; \
if (object->peers.next) \
head.next->peers.prev = 0; \
head.prev = object; \
object->peers.next = 0; \
}
#define QMGR_LIST_UNLINK(head, type, object, peers) { \
#define QMGR_LIST_UNLINK(head, type, object) { \
type next = object->peers.next; \
type prev = object->peers.prev; \
if (prev) prev->peers.next = next; \
@@ -55,16 +52,7 @@ typedef struct QMGR_SCAN QMGR_SCAN;
object->peers.next = object->peers.prev = 0; \
}
#define QMGR_LIST_LINK(head, pred, object, succ, peers) { \
object->peers.prev = pred; \
object->peers.next = succ; \
if (pred) pred->peers.next = object; \
else head.next = object; \
if (succ) succ->peers.prev = object; \
else head.prev = object; \
}
#define QMGR_LIST_PREPEND(head, object, peers) { \
#define QMGR_LIST_APPEND(head, object) { \
object->peers.next = head.next; \
object->peers.prev = 0; \
if (head.next) { \
@@ -75,7 +63,7 @@ typedef struct QMGR_SCAN QMGR_SCAN;
head.next = object; \
}
#define QMGR_LIST_APPEND(head, object, peers) { \
#define QMGR_LIST_PREPEND(head, object) { \
object->peers.prev = head.prev; \
object->peers.next = 0; \
if (head.prev) { \
@@ -114,40 +102,14 @@ struct QMGR_QUEUE_LIST {
QMGR_QUEUE *prev;
};
struct QMGR_JOB_LIST {
QMGR_JOB *next;
QMGR_JOB *prev;
};
struct QMGR_TRANSPORT {
int flags; /* blocked, etc. */
char *name; /* transport name */
int dest_concurrency_limit; /* concurrency per domain */
int init_dest_concurrency; /* init. per-domain concurrency */
int recipient_limit; /* recipients per transaction */
int rcpt_per_stack; /* extra slots reserved for jobs put
* on the job stack */
int rcpt_unused; /* available in-core recipient slots */
int slot_cost; /* cost of new preemption slot (# of
* selected entries) */
int slot_loan; /* preemption boost offset and */
int slot_loan_factor; /* factor, see qmgr_job_preempt() */
int min_slots; /* when preemption can take effect at
* all */
struct HTABLE *queue_byname; /* queues indexed by domain */
QMGR_QUEUE_LIST queue_list; /* queues, round robin order */
struct HTABLE *job_byname; /* jobs indexed by queue id */
QMGR_JOB_LIST job_list; /* list of message jobs (1 per
* message) ordered by scheduler */
QMGR_JOB_LIST job_bytime; /* jobs ordered by time since queued */
QMGR_JOB *job_current; /* keeps track of the current job */
QMGR_JOB *job_next_unread; /* next job with unread recipients */
QMGR_JOB *candidate_cache; /* cached result from
* qmgr_job_candidate() */
QMGR_JOB *candidate_cache_current; /* current job tied to the candidate */
time_t candidate_cache_time; /* when candidate_cache was last
* updated */
int blocker_tag; /* for marking blocker jobs */
QMGR_TRANSPORT_LIST peers; /* linkage */
char *reason; /* why unavailable */
};
@@ -168,7 +130,9 @@ extern QMGR_TRANSPORT *qmgr_transport_find(const char *);
* transactions. The "todo" queue contains messages that are to be delivered
* to this next hop. When a message is elected for transmission, it is moved
* from the "todo" queue to the "busy" queue. Messages are taken from the
* "todo" queue in round-robin order.
* "todo" queue in sequence. An initial destination delivery concurrency > 1
* ensures that one problematic message will not block all other traffic to
* that next hop.
*/
struct QMGR_ENTRY_LIST {
QMGR_ENTRY *next;
@@ -186,8 +150,7 @@ struct QMGR_QUEUE {
QMGR_ENTRY_LIST busy; /* messages on the wire */
QMGR_QUEUE_LIST peers; /* neighbor queues */
char *reason; /* why unavailable */
time_t clog_time_to_warn; /* time of last warning */
int blocker_tag; /* tagged if blocks job list */
time_t clog_time_to_warn; /* time of next warning */
};
#define QMGR_QUEUE_TODO 1 /* waiting for service */
@@ -196,6 +159,7 @@ struct QMGR_QUEUE {
extern int qmgr_queue_count;
extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *);
extern QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *);
extern void qmgr_queue_done(QMGR_QUEUE *);
extern void qmgr_queue_throttle(QMGR_QUEUE *, const char *);
extern void qmgr_queue_unthrottle(QMGR_QUEUE *);
@@ -234,15 +198,13 @@ struct QMGR_ENTRY {
QMGR_MESSAGE *message; /* message info */
QMGR_RCPT_LIST rcpt_list; /* as many as it takes */
QMGR_QUEUE *queue; /* parent linkage */
QMGR_PEER *peer; /* parent linkage */
QMGR_ENTRY_LIST queue_peers; /* per queue neighbor entries */
QMGR_ENTRY_LIST peer_peers; /* per peer neighbor entries */
QMGR_ENTRY_LIST peers; /* neighbor entries */
};
extern QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *);
extern void qmgr_entry_unselect(QMGR_ENTRY *);
extern QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *);
extern void qmgr_entry_unselect(QMGR_QUEUE *, QMGR_ENTRY *);
extern void qmgr_entry_done(QMGR_ENTRY *, int);
extern QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *, QMGR_MESSAGE *);
extern QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *, QMGR_MESSAGE *);
/*
* All common in-core information about a message is kept here. When all
@@ -259,8 +221,6 @@ struct QMGR_MESSAGE {
int refcount; /* queue entries */
int single_rcpt; /* send one rcpt at a time */
long arrival_time; /* time when queued */
time_t queued_time; /* time when moved to the active
* queue */
long warn_offset; /* warning bounce flag offset */
time_t warn_time; /* time next warning to be sent */
long data_offset; /* data seek offset */
@@ -281,11 +241,6 @@ struct QMGR_MESSAGE {
char *client_proto; /* client protocol */
char *client_helo; /* helo parameter */
QMGR_RCPT_LIST rcpt_list; /* complete addresses */
int rcpt_count; /* used recipient slots */
int rcpt_limit; /* maximum read in-core */
int rcpt_unread; /* # of recipients left in queue file */
QMGR_JOB_LIST job_list; /* jobs delivering this message (1
* per transport) */
};
/*
@@ -303,63 +258,6 @@ extern void qmgr_message_update_warn(QMGR_MESSAGE *);
extern QMGR_MESSAGE *qmgr_message_alloc(const char *, const char *, int);
extern QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *);
/*
* Sometimes it's required to access the transport queues and entries on per
* message basis. That's what the QMGR_JOB structure is for - it groups all
* per message information within each transport using a list of QMGR_PEER
* structures. These structures in turn correspond with per message
* QMGR_QUEUE structure and list all per message QMGR_ENTRY structures.
*/
struct QMGR_PEER_LIST {
QMGR_PEER *next;
QMGR_PEER *prev;
};
struct QMGR_JOB {
QMGR_MESSAGE *message; /* message delivered by this job */
QMGR_TRANSPORT *transport; /* transport this job belongs to */
QMGR_JOB_LIST message_peers; /* per message neighbor linkage */
QMGR_JOB_LIST transport_peers; /* per transport neighbor linkage */
QMGR_JOB_LIST time_peers; /* by time neighbor linkage */
QMGR_JOB *stack_parent; /* stack parent */
QMGR_JOB_LIST stack_children; /* all stack children */
QMGR_JOB_LIST stack_siblings; /* stack children linkage */
int stack_level; /* job stack nesting level (-1 means
* it's not on the lists at all) */
int blocker_tag; /* tagged if blocks the job list */
struct HTABLE *peer_byname; /* message job peers, indexed by
* domain */
QMGR_PEER_LIST peer_list; /* list of message job peers */
int slots_used; /* slots used during preemption */
int slots_available; /* slots available for preemption (in
* multiples of slot_cost) */
int selected_entries; /* # of entries selected for delivery
* so far */
int read_entries; /* # of entries read in-core so far */
int rcpt_count; /* used recipient slots */
int rcpt_limit; /* available recipient slots */
};
struct QMGR_PEER {
QMGR_JOB *job; /* job handling this peer */
QMGR_QUEUE *queue; /* queue corresponding with this peer */
int refcount; /* peer entries */
QMGR_ENTRY_LIST entry_list; /* todo message entries queued for
* this peer */
QMGR_PEER_LIST peers; /* neighbor linkage */
};
extern QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *);
extern QMGR_PEER *qmgr_peer_select(QMGR_JOB *);
extern QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *, QMGR_TRANSPORT *);
extern void qmgr_job_free(QMGR_JOB *);
extern void qmgr_job_move_limits(QMGR_JOB *);
extern QMGR_PEER *qmgr_peer_create(QMGR_JOB *, QMGR_QUEUE *);
extern QMGR_PEER *qmgr_peer_find(QMGR_JOB *, QMGR_QUEUE *);
extern void qmgr_peer_free(QMGR_PEER *);
/*
* qmgr_defer.c
*/
@@ -431,9 +329,4 @@ extern char *qmgr_scan_next(QMGR_SCAN *);
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/

View File

@@ -35,11 +35,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */

View File

@@ -60,11 +60,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -132,7 +127,7 @@ void qmgr_defer_todo(QMGR_QUEUE *queue, const char *reason)
* Proceed carefully. Queue entries will disappear as a side effect.
*/
for (entry = queue->todo.next; entry != 0; entry = next) {
next = entry->queue_peers.next;
next = entry->peers.next;
message = entry->message;
for (nrcpt = 0; nrcpt < entry->rcpt_list.len; nrcpt++) {
recipient = entry->rcpt_list.info + nrcpt;

View File

@@ -39,11 +39,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -287,6 +282,7 @@ static void qmgr_deliver_update(int unused_event, char *context)
void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
{
QMGR_QUEUE *queue;
QMGR_ENTRY *entry;
/*
@@ -310,7 +306,8 @@ void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
* agent request reading routine is prepared for the queue manager to
* change its mind for no apparent reason.
*/
if ((entry = qmgr_job_entry_select(transport)) == 0) {
if ((queue = qmgr_queue_select(transport)) == 0
|| (entry = qmgr_entry_select(queue)) == 0) {
(void) vstream_fclose(stream);
return;
}
@@ -322,10 +319,10 @@ void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
* while some other queue manipulation is happening.
*/
if (qmgr_deliver_send_request(entry, stream) < 0) {
qmgr_entry_unselect(entry);
qmgr_entry_unselect(queue, entry);
qmgr_transport_throttle(transport, "mail transport unavailable");
qmgr_defer_transport(transport, transport->reason);
/* warning: entry may be a dangling pointer here */
/* warning: entry and queue may be dangling pointers here */
(void) vstream_fclose(stream);
return;
}

View File

@@ -6,8 +6,8 @@
/* SYNOPSIS
/* #include "qmgr.h"
/*
/* QMGR_ENTRY *qmgr_entry_create(peer, message)
/* QMGR_PEER *peer;
/* QMGR_ENTRY *qmgr_entry_create(queue, message)
/* QMGR_QUEUE *queue;
/* QMGR_MESSAGE *message;
/*
/* void qmgr_entry_done(entry, which)
@@ -24,8 +24,8 @@
/* These routines add/delete/manipulate per-site message
/* delivery requests.
/*
/* qmgr_entry_create() creates an entry for the named peer and message,
/* and appends the entry to the peer's list and its queue's todo list.
/* qmgr_entry_create() creates an entry for the named queue and
/* message, and appends the entry to the queue's todo list.
/* Filling in and cleaning up the recipients is the responsibility
/* of the caller.
/*
@@ -36,9 +36,6 @@
/* of the site's `todo' list (i.e. queue entries awaiting selection
/* for actual delivery).
/*
/* qmgr_entry_done() discards its peer structure when the peer
/* is not referenced anymore.
/*
/* qmgr_entry_done() triggers cleanup of the per-site queue when
/* the site has no pending deliveries, and the site is either
/* alive, or the site is dead and the number of in-core queues
@@ -50,14 +47,14 @@
/* the queue file to the deferred queue; send bounce reports to the
/* message originator (see qmgr_active_done()).
/*
/* qmgr_entry_select() selects first entry from the named
/* qmgr_entry_select() selects the next entry from the named
/* per-site queue's `todo' list for actual delivery. The entry is
/* moved to the queue's `busy' list: the list of messages being
/* delivered. The entry is also removed from its peer list.
/* delivered.
/*
/* qmgr_entry_unselect() takes the named entry off the named
/* per-site queue's `busy' list and moves it to the queue's
/* `todo' list. The entry is also appended to its peer list again.
/* `todo' list.
/* DIAGNOSTICS
/* Panic: interface violations, internal inconsistencies.
/* LICENSE
@@ -69,11 +66,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -99,36 +91,27 @@
/* qmgr_entry_select - select queue entry for delivery */
QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *peer)
QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *queue)
{
QMGR_ENTRY *entry;
QMGR_QUEUE *queue;
if ((entry = peer->entry_list.next) != 0) {
queue = entry->queue;
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
if ((entry = queue->todo.prev) != 0) {
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry);
queue->todo_refcount--;
QMGR_LIST_APPEND(queue->busy, entry, queue_peers);
QMGR_LIST_APPEND(queue->busy, entry);
queue->busy_refcount++;
QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
peer->job->selected_entries++;
}
return (entry);
}
/* qmgr_entry_unselect - unselect queue entry for delivery */
void qmgr_entry_unselect(QMGR_ENTRY *entry)
void qmgr_entry_unselect(QMGR_QUEUE *queue, QMGR_ENTRY *entry)
{
QMGR_PEER *peer = entry->peer;
QMGR_QUEUE *queue = entry->queue;
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers);
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry);
queue->busy_refcount--;
QMGR_LIST_APPEND(queue->todo, entry, queue_peers);
QMGR_LIST_APPEND(queue->todo, entry);
queue->todo_refcount++;
QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers);
peer->job->selected_entries--;
}
/* qmgr_entry_done - dispose of queue entry */
@@ -137,10 +120,6 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which)
{
QMGR_QUEUE *queue = entry->queue;
QMGR_MESSAGE *message = entry->message;
QMGR_PEER *peer = entry->peer;
QMGR_JOB *sponsor,
*job = peer->job;
QMGR_TRANSPORT *transport = job->transport;
/*
* Take this entry off the in-core queue.
@@ -148,76 +127,24 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which)
if (entry->stream != 0)
msg_panic("qmgr_entry_done: file is open");
if (which == QMGR_QUEUE_BUSY) {
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers);
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry);
queue->busy_refcount--;
} else if (which == QMGR_QUEUE_TODO) {
QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
job->selected_entries++;
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry);
queue->todo_refcount--;
} else {
msg_panic("qmgr_entry_done: bad queue spec: %d", which);
}
/*
* Decrease the in-core recipient counts and free the recipient list and
* the structure itself.
* Free the recipient list and decrease the in-core recipient count
* accordingly.
*/
job->rcpt_count -= entry->rcpt_list.len;
message->rcpt_count -= entry->rcpt_list.len;
qmgr_recipient_count -= entry->rcpt_list.len;
qmgr_rcpt_list_free(&entry->rcpt_list);
myfree((char *) entry);
/*
* Make sure that the transport of any retired or finishing job that
* donated recipient slots to this message gets them back first. Then, if
* possible, pass the remaining unused recipient slots to the next job on
* the job list.
*/
for (sponsor = message->job_list.next; sponsor; sponsor = sponsor->message_peers.next) {
if (sponsor->rcpt_count >= sponsor->rcpt_limit || sponsor == job)
continue;
if (sponsor->stack_level < 0 || message->rcpt_offset == 0)
qmgr_job_move_limits(sponsor);
}
if (message->rcpt_offset == 0) {
qmgr_job_move_limits(job);
}
/*
* If the queue was blocking some of the jobs on the job list, check if
* the concurrency limit has lifted. If there are still some pending
* deliveries, give it a try and unmark all transport blockers at once.
* The qmgr_job_entry_select() will do the rest. In either case make sure
* the queue is not marked as a blocker anymore, with extra handling of
* queues which were declared dead.
*
* Note that changing the blocker status also affects the candidate cache.
* Most of the cases would be automatically recognized by the current job
* change, but we play safe and reset the cache explicitly below.
*
* Keeping the transport blocker tag odd is an easy way to make sure the tag
* never matches jobs that are not explicitly marked as blockers.
*/
if (queue->blocker_tag == transport->blocker_tag) {
if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
transport->blocker_tag += 2;
transport->job_current = transport->job_list.next;
transport->candidate_cache_current = 0;
}
if (queue->window > queue->busy_refcount || queue->window == 0)
queue->blocker_tag = 0;
}
/*
* When there are no more entries for this peer, discard the peer
* structure.
*/
peer->refcount--;
if (peer->refcount == 0)
qmgr_peer_free(peer);
/*
* When the in-core queue for this site is empty and when this site is
* not dead, discard the in-core queue. When this site is dead, but the
@@ -234,18 +161,33 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which)
/*
* Update the in-core message reference count. When the in-core message
* structure has no more references, dispose of the message.
*
* When the in-core recipient count falls below a threshold, and this
* message has more recipients, read more recipients now. If we read more
* recipients as soon as the recipient count falls below the in-core
* recipient limit, we do not give other messages a chance until this
* message is delivered. That's good for mailing list deliveries, bad for
* one-to-one mail. If we wait until the in-core recipient count drops
* well below the in-core recipient limit, we give other mail a chance,
* but we also allow list deliveries to become interleaved. In the worst
* case, people near the start of a mailing list get a burst of postings
* today, while people near the end of the list get that same burst of
* postings a whole day later.
*/
#define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0))
message->refcount--;
if (message->rcpt_offset > 0
&& qmgr_recipient_count < FUDGE(var_qmgr_rcpt_limit))
qmgr_message_realloc(message);
if (message->refcount == 0)
qmgr_active_done(message);
}
/* qmgr_entry_create - create queue todo entry */
QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message)
QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message)
{
QMGR_ENTRY *entry;
QMGR_QUEUE *queue = peer->queue;
/*
* Sanity check.
@@ -261,11 +203,8 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message)
entry->message = message;
qmgr_rcpt_list_init(&entry->rcpt_list);
message->refcount++;
entry->peer = peer;
QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers);
peer->refcount++;
entry->queue = queue;
QMGR_LIST_APPEND(queue->todo, entry, queue_peers);
QMGR_LIST_APPEND(queue->todo, entry);
queue->todo_refcount++;
/*

View File

@@ -49,10 +49,7 @@
/* qmgr_message_realloc() resumes reading recipients from the queue
/* file, and updates the recipient list and \fIrcpt_offset\fR message
/* structure members. A null result means that the file could not be
/* read or that the file contained incorrect information. Recipient
/* limit imposed this time is based on the position of the message
/* job(s) on corresponding transport job list(s). It's considered
/* an error to call this when the recipient slots can't be allocated.
/* read or that the file contained incorrect information.
/*
/* qmgr_message_free() destroys an in-core message structure and makes
/* the resources available for reuse. It is an error to destroy
@@ -73,11 +70,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -106,7 +98,6 @@
#include <argv.h>
#include <stringops.h>
#include <myflock.h>
#include <sane_time.h>
/* Global library. */
@@ -152,7 +143,6 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name,
message->refcount = 0;
message->single_rcpt = 0;
message->arrival_time = 0;
message->queued_time = sane_time();
message->data_offset = 0;
message->queue_id = mystrdup(queue_id);
message->queue_name = mystrdup(queue_name);
@@ -173,10 +163,6 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name,
message->client_proto = 0;
message->client_helo = 0;
qmgr_rcpt_list_init(&message->rcpt_list);
message->rcpt_count = 0;
message->rcpt_limit = var_qmgr_msg_rcpt_limit;
message->rcpt_unread = 0;
QMGR_LIST_INIT(message->job_list);
return (message);
}
@@ -244,7 +230,6 @@ static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
* so we set data_size to this as well and ignore the size record itself
* completely.
*/
message->rcpt_unread = 0;
for (;;) {
rec_type = rec_get(message->fp, buf, 0);
if (rec_type <= 0)
@@ -255,10 +240,6 @@ static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
msg_info("old-style scan record %c %s", rec_type, start);
if (rec_type == REC_TYPE_END)
break;
if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_RCPT) {
message->rcpt_unread++;
continue;
}
if (rec_type == REC_TYPE_MESG) {
if (message->data_offset == 0) {
if ((message->data_offset = vstream_ftell(message->fp)) < 0)
@@ -297,9 +278,8 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
int rec_type;
long curr_offset;
long save_offset = message->rcpt_offset; /* save a flag */
int save_unread = message->rcpt_unread; /* save a count */
char *start;
int recipient_limit;
int nrcpt = 0;
const char *error_text;
char *name;
char *value;
@@ -314,15 +294,6 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
/*
* If we re-open this file, skip over on-file recipient records that we
* already looked at, and refill the in-core recipient address list.
*
* For the first time, the message recipient limit is calculated from the
* global recipient limit. This is to avoid reading little recipients
* when the active queue is near empty. When the queue becomes full, only
* the necessary amount is read in core. Such priming is necessary
* because there are no message jobs yet.
*
* For the next time, the recipient limit is based solely on the message
* jobs' positions in the job lists and/or job stacks.
*/
if (message->rcpt_offset) {
if (message->rcpt_list.len)
@@ -331,29 +302,26 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0)
msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
message->rcpt_offset = 0;
recipient_limit = message->rcpt_limit - message->rcpt_count;
} else {
recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count;
if (recipient_limit < message->rcpt_limit)
recipient_limit = message->rcpt_limit;
}
if (recipient_limit <= 0)
msg_panic("%s: no recipient slots available", message->queue_id);
/*
* Read envelope records. XXX Rely on the front-end programs to enforce
* record size limits. Read up to recipient_limit recipients from the
* record size limits. Read up to var_qmgr_rcpt_limit recipients from the
* queue file, to protect against memory exhaustion. Recipient records
* may appear before or after the message content, so we keep reading
* from the queue file until we have enough recipients (rcpt_offset != 0)
* and until we know all the non-recipient information.
*
* Note that the total recipient count record is accurate only for fresh
* queue files. After some of the recipients are marked as done and the
* queue file is deferred, it can be used as upper bound estimate only.
* Fortunately, this poses no major problem on the scheduling algorithm,
* as the only impact is that the already deferred messages are not
* chosen by qmgr_job_candidate() as often as they could.
* When reading recipients from queue file, stop reading when we reach a
* per-message in-core recipient limit rather than a global in-core
* recipient limit. Use the global recipient limit only in order to stop
* opening queue files. The purpose is to achieve equal delay for
* messages with recipient counts up to var_qmgr_rcpt_limit recipients.
*
* If we would read recipients up to a global recipient limit, the average
* number of in-core recipients per message would asymptotically approach
* (global recipient limit)/(active queue size limit), which gives equal
* delay per recipient rather than equal delay per message.
*
* On the first open, we must examine all non-recipient records.
*
@@ -390,15 +358,15 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
*/
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_offset == 0) {
message->rcpt_unread--;
qmgr_rcpt_list_add(&message->rcpt_list, curr_offset,
orig_rcpt ? orig_rcpt : "", start);
if (orig_rcpt) {
myfree(orig_rcpt);
orig_rcpt = 0;
}
if (message->rcpt_list.len >= recipient_limit) {
if (message->rcpt_list.len >= FUDGE(var_qmgr_rcpt_limit)) {
if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
msg_fatal("vstream_ftell %s: %m",
VSTREAM_PATH(message->fp));
@@ -424,13 +392,10 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
continue;
}
if (rec_type == REC_TYPE_DONE) {
if (message->rcpt_offset == 0) {
message->rcpt_unread--;
if (orig_rcpt) {
myfree(orig_rcpt);
orig_rcpt = 0;
}
}
continue;
}
if (orig_rcpt != 0) {
@@ -457,7 +422,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
if (message->data_offset == 0) {
if ((count = sscanf(start, "%ld %ld %d %d",
&message->data_size, &message->data_offset,
&message->rcpt_unread, &message->rflags)) >= 3) {
&nrcpt, &message->rflags)) >= 3) {
/* Postfix >= 1.0 (a.k.a. 20010228). */
if (message->data_offset <= 0 || message->data_size <= 0) {
msg_warn("%s: invalid size record: %.100s",
@@ -511,7 +476,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
if (message->sender == 0) {
message->sender = mystrdup(start);
opened(message->queue_id, message->sender,
message->data_size, message->rcpt_unread,
message->data_size, nrcpt,
"queue %s", message->queue_name);
}
continue;
@@ -629,12 +594,6 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
* Sanity checks. Verify that all required information was found,
* including the queue file end marker.
*/
if (message->rcpt_unread < 0
|| (message->rcpt_offset == 0 && message->rcpt_unread != 0)) {
msg_warn("%s: rcpt count mismatch (%d)",
message->queue_id, message->rcpt_unread);
message->rcpt_unread = 0;
}
if (rec_type <= 0) {
/* Already logged warning. */
} else if (message->arrival_time == 0) {
@@ -650,9 +609,6 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
return (0);
}
message->rcpt_offset = save_offset; /* restore flag */
message->rcpt_unread = save_unread; /* restore count */
qmgr_rcpt_list_free(&message->rcpt_list);
qmgr_rcpt_list_init(&message->rcpt_list);
return (-1);
}
@@ -729,7 +685,7 @@ static int qmgr_message_sort_compare(const void *p1, const void *p2)
/*
* Compare recipient address.
*/
return (strcmp(rcpt1->address, rcpt2->address));
return (strcasecmp(rcpt1->address, rcpt2->address));
}
/* qmgr_message_sort - sort message recipient addresses by domain */
@@ -1014,8 +970,6 @@ static void qmgr_message_assign(QMGR_MESSAGE *message)
QMGR_RCPT *recipient;
QMGR_ENTRY *entry = 0;
QMGR_QUEUE *queue;
QMGR_JOB *job = 0;
QMGR_PEER *peer = 0;
/*
* Try to bundle as many recipients in a delivery request as we can. When
@@ -1031,87 +985,27 @@ static void qmgr_message_assign(QMGR_MESSAGE *message)
for (recipient = list.info; recipient < list.info + list.len; recipient++) {
if ((queue = recipient->queue) != 0) {
if (message->single_rcpt || entry == 0 || entry->queue != queue
|| !LIMIT_OK(queue->transport->recipient_limit,
|| !LIMIT_OK(entry->queue->transport->recipient_limit,
entry->rcpt_list.len)) {
/*
* Lookup or instantiate the message job if necessary.
*/
if (job == 0 || queue->transport != job->transport) {
job = qmgr_job_obtain(message, queue->transport);
peer = 0;
entry = qmgr_entry_create(queue, message);
}
/*
* Lookup or instantiate job peer if necessary.
*/
if (peer == 0 || queue != peer->queue) {
if ((peer = qmgr_peer_find(job, queue)) == 0)
peer = qmgr_peer_create(job, queue);
}
/*
* Create new peer entry.
*/
entry = qmgr_entry_create(peer, message);
job->read_entries++;
}
/*
* Add the recipient to the current entry and increase all those
* recipient counters accordingly.
*/
qmgr_rcpt_list_add(&entry->rcpt_list, recipient->offset,
recipient->orig_rcpt, recipient->address);
job->rcpt_count++;
message->rcpt_count++;
qmgr_recipient_count++;
}
}
/*
* Release the message recipient list and reinitialize it for the next
* time.
*/
qmgr_rcpt_list_free(&message->rcpt_list);
qmgr_rcpt_list_init(&message->rcpt_list);
/*
* Note that even if qmgr_job_obtain() reset the job candidate cache of
* all transports to which we assigned new recipients, this message may
* have other jobs which we didn't touch at all this time. But the number
* of unread recipients affecting the candidate selection might have
* changed considerably, so we must invalidate the caches if it might be
* of some use.
*/
for (job = message->job_list.next; job; job = job->message_peers.next)
if (job->selected_entries < job->read_entries
&& job->blocker_tag != job->transport->blocker_tag)
job->transport->candidate_cache_current = 0;
}
/* qmgr_message_move_limits - recycle unused recipient slots */
static void qmgr_message_move_limits(QMGR_MESSAGE *message)
{
QMGR_JOB *job;
for (job = message->job_list.next; job; job = job->message_peers.next)
qmgr_job_move_limits(job);
}
/* qmgr_message_free - release memory for in-core message structure */
void qmgr_message_free(QMGR_MESSAGE *message)
{
QMGR_JOB *job;
if (message->refcount != 0)
msg_panic("qmgr_message_free: reference len: %d", message->refcount);
if (message->fp)
msg_panic("qmgr_message_free: queue file is open");
while ((job = message->job_list.next) != 0)
qmgr_job_free(job);
myfree(message->queue_id);
myfree(message->queue_name);
if (message->encoding)
@@ -1196,8 +1090,6 @@ QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id,
qmgr_message_sort(message);
qmgr_message_assign(message);
qmgr_message_close(message);
if (message->rcpt_offset == 0)
qmgr_message_move_limits(message);
return (message);
}
}
@@ -1232,8 +1124,6 @@ QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *message)
qmgr_message_sort(message);
qmgr_message_assign(message);
qmgr_message_close(message);
if (message->rcpt_offset == 0)
qmgr_message_move_limits(message);
return (message);
}
}

View File

@@ -20,6 +20,9 @@
/* QMGR_TRANSPORT *transport;
/* const char *name;
/*
/* QMGR_QUEUE *qmgr_queue_select(transport)
/* QMGR_TRANSPORT *transport;
/*
/* void qmgr_queue_throttle(queue, reason)
/* QMGR_QUEUE *queue;
/* const char *reason;
@@ -49,6 +52,10 @@
/* qmgr_queue_find() looks up the named queue for the named
/* transport. A null result means that the queue was not found.
/*
/* qmgr_queue_select() uses a round-robin strategy to select
/* from the named transport one per-destination queue with a
/* non-empty `todo' list.
/*
/* qmgr_queue_throttle() handles a delivery error, and decrements the
/* concurrency limit for the destination. When the concurrency limit
/* for a destination becomes zero, qmgr_queue_throttle() starts a timer
@@ -60,7 +67,7 @@
/* limit specified for the transport. This routine implements
/* "slow open" mode, and eliminates the "thundering herd" problem.
/* DIAGNOSTICS
/* Panic: consistency check failure.
/* None
/* LICENSE
/* .ad
/* .fi
@@ -70,11 +77,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -146,7 +148,7 @@ void qmgr_queue_unthrottle(QMGR_QUEUE *queue)
*/
if (transport->dest_concurrency_limit == 0
|| transport->dest_concurrency_limit > queue->window)
if (queue->window < queue->busy_refcount + transport->init_dest_concurrency)
if (queue->window <= queue->busy_refcount + transport->init_dest_concurrency)
queue->window++;
}
@@ -184,6 +186,27 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, const char *reason)
}
}
/* qmgr_queue_select - select in-core queue for delivery */
QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *transport)
{
QMGR_QUEUE *queue;
/*
* If we find a suitable site, rotate the list to enforce round-robin
* selection. See similar selection code in qmgr_transport_select().
*/
for (queue = transport->queue_list.next; queue; queue = queue->peers.next) {
if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
QMGR_LIST_ROTATE(transport->queue_list, queue);
if (msg_verbose)
msg_info("qmgr_queue_select: %s", queue->name);
return (queue);
}
}
return (0);
}
/* qmgr_queue_done - delete in-core queue for site */
void qmgr_queue_done(QMGR_QUEUE *queue)
@@ -209,7 +232,7 @@ void qmgr_queue_done(QMGR_QUEUE *queue)
/*
* Clean up this in-core queue.
*/
QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue, peers);
QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue);
htable_delete(transport->queue_byname, queue->name, (void (*) (char *)) 0);
myfree(queue->name);
myfree(queue->nexthop);
@@ -241,8 +264,7 @@ QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name,
QMGR_LIST_INIT(queue->busy);
queue->reason = 0;
queue->clog_time_to_warn = 0;
queue->blocker_tag = 0;
QMGR_LIST_APPEND(transport->queue_list, queue, peers);
QMGR_LIST_PREPEND(transport->queue_list, queue);
htable_enter(transport->queue_byname, name, (char *) queue);
return (queue);
}

View File

@@ -64,11 +64,6 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -238,7 +233,7 @@ QMGR_TRANSPORT *qmgr_transport_select(void)
/*
* If we find a suitable transport, rotate the list of transports to
* effectuate round-robin selection. See similar selection code in
* qmgr_peer_select().
* qmgr_queue_select().
*/
#define STAY_AWAY (QMGR_TRANSPORT_STAT_BUSY | QMGR_TRANSPORT_STAT_DEAD)
@@ -247,7 +242,7 @@ QMGR_TRANSPORT *qmgr_transport_select(void)
continue;
for (queue = xport->queue_list.next; queue; queue = queue->peers.next) {
if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
QMGR_LIST_ROTATE(qmgr_transport_list, xport, peers);
QMGR_LIST_ROTATE(qmgr_transport_list, xport);
if (msg_verbose)
msg_info("qmgr_transport_select: %s", xport->name);
return (xport);
@@ -324,10 +319,10 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name)
* Use global configuration settings or transport-specific settings.
*/
transport->dest_concurrency_limit =
get_mail_conf_int2(name, _DEST_CON_LIMIT,
get_mail_conf_int2(name, "_destination_concurrency_limit",
var_dest_con_limit, 0, 0);
transport->recipient_limit =
get_mail_conf_int2(name, _DEST_RCPT_LIMIT,
get_mail_conf_int2(name, "_destination_recipient_limit",
var_dest_rcpt_limit, 0, 0);
if (transport->dest_concurrency_limit == 0
@@ -336,36 +331,13 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name)
else
transport->init_dest_concurrency = transport->dest_concurrency_limit;
transport->slot_cost = get_mail_conf_int2(name, _DELIVERY_SLOT_COST,
var_delivery_slot_cost, 0, 0);
transport->slot_loan = get_mail_conf_int2(name, _DELIVERY_SLOT_LOAN,
var_delivery_slot_loan, 0, 0);
transport->slot_loan_factor =
100 - get_mail_conf_int2(name, _DELIVERY_SLOT_DISCOUNT,
var_delivery_slot_discount, 0, 100);
transport->min_slots = get_mail_conf_int2(name, _MIN_DELIVERY_SLOTS,
var_min_delivery_slots, 0, 0);
transport->rcpt_unused = get_mail_conf_int2(name, _XPORT_RCPT_LIMIT,
var_xport_rcpt_limit, 0, 0);
transport->rcpt_per_stack = get_mail_conf_int2(name, _STACK_RCPT_LIMIT,
var_stack_rcpt_limit, 0, 0);
transport->queue_byname = htable_create(0);
QMGR_LIST_INIT(transport->queue_list);
transport->job_byname = htable_create(0);
QMGR_LIST_INIT(transport->job_list);
QMGR_LIST_INIT(transport->job_bytime);
transport->job_current = 0;
transport->job_next_unread = 0;
transport->candidate_cache = 0;
transport->candidate_cache_current = 0;
transport->candidate_cache_time = (time_t) 0;
transport->blocker_tag = 1;
transport->reason = 0;
if (qmgr_transport_byname == 0)
qmgr_transport_byname = htable_create(10);
htable_enter(qmgr_transport_byname, name, (char *) transport);
QMGR_LIST_PREPEND(qmgr_transport_list, transport, peers);
QMGR_LIST_APPEND(qmgr_transport_list, transport);
if (msg_verbose)
msg_info("qmgr_transport_create: %s concurrency %d recipients %d",
transport->name, transport->dest_concurrency_limit,

View File

@@ -234,6 +234,7 @@ static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup,
{
time_t now = time((time_t *) 0);
int status;
char *name;
/*
* Protect against time-warped time stamps. Warn about mail that has been
@@ -282,8 +283,20 @@ static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup,
info->id, (long) info->st.st_uid);
return (REMOVE_MESSAGE_FILE);
}
/*
* For messages belonging to $mail_owner also log the maildrop queue id.
* This supports message tracking for mail requeued via "postsuper -r".
*/
if (info->st.st_uid == var_owner_uid) {
msg_info("%s: uid=%d from=<%s> orig_id=%s", info->id,
(int) info->st.st_uid, info->sender,
((name = strrchr(info->path, '/')) ?
name + 1 : info->path));
} else {
msg_info("%s: uid=%d from=<%s>", info->id,
(int) info->st.st_uid, info->sender);
}
/*
* Message content segment. Send a dummy message length. Prepend a

View File

@@ -1,9 +1,11 @@
SHELL = /bin/sh
SRCS = qmgr.c qmgr_active.c qmgr_transport.c qmgr_queue.c qmgr_entry.c \
qmgr_message.c qmgr_deliver.c qmgr_move.c qmgr_rcpt_list.c \
qmgr_job.c qmgr_peer.c \
qmgr_defer.c qmgr_enable.c qmgr_scan.c qmgr_bounce.c
OBJS = qmgr.o qmgr_active.o qmgr_transport.o qmgr_queue.o qmgr_entry.o \
qmgr_message.o qmgr_deliver.o qmgr_move.o qmgr_rcpt_list.o \
qmgr_job.o qmgr_peer.o \
qmgr_defer.o qmgr_enable.o qmgr_scan.o qmgr_bounce.o
HDRS = qmgr.h
TESTSRC =
@@ -29,7 +31,7 @@ tests: test
update: ../../libexec/$(PROG)
../../libexec/$(PROG): $(PROG)
cp $(PROG) ../../libexec
cp $(PROG) ../../libexec/$(PROG)
printfck: $(OBJS) $(PROG)
rm -rf printfck
@@ -156,6 +158,16 @@ qmgr_entry.o: ../../include/vbuf.h
qmgr_entry.o: ../../include/mail_params.h
qmgr_entry.o: qmgr.h
qmgr_entry.o: ../../include/scan_dir.h
qmgr_job.o: qmgr_job.c
qmgr_job.o: ../../include/sys_defs.h
qmgr_job.o: ../../include/msg.h
qmgr_job.o: ../../include/htable.h
qmgr_job.o: ../../include/mymalloc.h
qmgr_job.o: ../../include/sane_time.h
qmgr_job.o: qmgr.h
qmgr_job.o: ../../include/vstream.h
qmgr_job.o: ../../include/vbuf.h
qmgr_job.o: ../../include/scan_dir.h
qmgr_message.o: qmgr_message.c
qmgr_message.o: ../../include/sys_defs.h
qmgr_message.o: ../../include/msg.h
@@ -168,6 +180,7 @@ qmgr_message.o: ../../include/valid_hostname.h
qmgr_message.o: ../../include/argv.h
qmgr_message.o: ../../include/stringops.h
qmgr_message.o: ../../include/myflock.h
qmgr_message.o: ../../include/sane_time.h
qmgr_message.o: ../../include/dict.h
qmgr_message.o: ../../include/mail_queue.h
qmgr_message.o: ../../include/mail_params.h
@@ -199,6 +212,15 @@ qmgr_move.o: ../../include/vbuf.h
qmgr_move.o: ../../include/vstream.h
qmgr_move.o: ../../include/mail_scan_dir.h
qmgr_move.o: qmgr.h
qmgr_peer.o: qmgr_peer.c
qmgr_peer.o: ../../include/sys_defs.h
qmgr_peer.o: ../../include/msg.h
qmgr_peer.o: ../../include/htable.h
qmgr_peer.o: ../../include/mymalloc.h
qmgr_peer.o: qmgr.h
qmgr_peer.o: ../../include/vstream.h
qmgr_peer.o: ../../include/vbuf.h
qmgr_peer.o: ../../include/scan_dir.h
qmgr_queue.o: qmgr_queue.c
qmgr_queue.o: ../../include/sys_defs.h
qmgr_queue.o: ../../include/msg.h

View File

@@ -80,6 +80,10 @@
/* .IP "\fBdestination status cache\fR"
/* The queue manager avoids unnecessary delivery attempts by
/* maintaining a short-term, in-memory list of unreachable destinations.
/* .IP "\fBpreemptive message scheduling\fR"
/* The queue manager attempts to minimize the average per-recipient delay
/* while still preserving the correct per-message delays, using
/* a sophisticated preemptive message scheduling.
/* TRIGGERS
/* .ad
/* .fi
@@ -151,6 +155,8 @@
/* .SH "Active queue controls"
/* .ad
/* .fi
/* In the text below, \fItransport\fR is the first field in a
/* \fBmaster.cf\fR entry.
/* .IP \fBqmgr_clog_warn_time\fR
/* Minimal delay between warnings that a specific destination
/* is clogging up the active queue. Specify 0 to disable.
@@ -161,6 +167,19 @@
/* .sp
/* This parameter also limits the size of the short-term, in-memory
/* destination cache.
/* .IP \fBqmgr_message_recipient_minimum\fR
/* Per message minimum of in-memory recipients.
/* .IP \fBdefault_recipient_limit\fR
/* Default limit on the number of in-memory recipients per transport.
/* .IP \fItransport\fB_recipient_limit\fR
/* Limit on the number of in-memory recipients, for the named
/* message \fItransport\fR.
/* .IP \fBdefault_extra_recipient_limit\fR
/* Default limit on the total number of per transport in-memory
/* recipients that the preempting messages can have.
/* .IP \fItransport\fB_extra_recipient_limit\fR
/* Limit on the number of in-memory recipients which all preempting
/* messages delivered by the transport \fItransport\fR can have.
/* .SH "Timing controls"
/* .ad
/* .fi
@@ -188,19 +207,6 @@
/* .SH "Concurrency controls"
/* .ad
/* .fi
/* In the text below, \fItransport\fR is the first field in a
/* \fBmaster.cf\fR entry.
/* .IP "\fBqmgr_fudge_factor\fR (valid range: 10..100)"
/* The percentage of delivery resources that a busy mail system will
/* use up for delivery of a large mailing list message.
/* With 100%, delivery of one message does not begin before the previous
/* message has been delivered. This results in good performance for large
/* mailing lists, but results in poor response time for one-to-one mail.
/* With less than 100%, response time for one-to-one mail improves,
/* but large mailing list delivery performance suffers. In the worst
/* case, recipients near the beginning of a large list receive a burst
/* of messages immediately, while recipients near the end of that list
/* receive that same burst of messages a whole day later.
/* .IP \fBinitial_destination_concurrency\fR
/* Initial per-destination concurrency level for parallel delivery
/* to the same destination.
@@ -218,6 +224,40 @@
/* .IP \fItransport\fB_destination_recipient_limit\fR
/* Limit on the number of recipients per message transfer, for the
/* named message \fItransport\fR.
/* .SH "Message scheduling"
/* .ad
/* .fi
/* .IP "\fItransport\fB_delivery_slot_cost\fR (valid range: 0,2,3...)
/* This parameter basically controls how often a message
/* delivered by \fItransport\fR can be preempted by another
/* message.
/* An internal per-message/transport counter is incremented by one
/* for each \fItransport\fB_delivery_slot_cost\fR
/* deliveries handled by \fItransport\fR. This counter represents
/* the number of "available delivery slots" for use by other messages.
/* Current message can be preempted by another message when that
/* other message can be delivered using less \fItransport\fR agents
/* than the value of the "available delivery slots" counter.
/* .sp
/* Value equal to 0 disables the message preemption for \fItransport\fR.
/* .IP \fItransport\fB_minimum_delivery_slots\fR
/* Message preemption is not attempted at all whenever a message
/* that can't ever accumulate at least \fItransport\fB_minimum_delivery_slots\fR
/* available delivery slots is being delivered by \fItransport\fR.
/* .IP "\fItransport\fB_delivery_slot_discount\fR (valid range: 0..100)"
/* .IP \fItransport\fB_delivery_slot_loan\fR
/* These parameters speed up the moment when a message preemption can happen.
/* Instead of waiting until the full amount of delivery slots
/* required is available, the preemption can happen when
/* \fItransport\fB_delivery_slot_discount\fR percent of the required
/* amount plus \fItransport\fB_delivery_slot_loan\fR still remains to
/* be accumulated. Note that the full amount will still have to be
/* accumulated before another preemption can take place later.
/* .IP \fBdefault_delivery_slot_cost\fR
/* .IP \fBdefault_minimum_delivery_slots\fR
/* .IP \fBdefault_delivery_slot_discount\fR
/* .IP \fBdefault_delivery_slot_loan\fR
/* Default values for the transport specific parameters described above.
/* SEE ALSO
/* master(8), process manager
/* syslogd(8) system logging
@@ -231,6 +271,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -275,15 +320,21 @@ int var_max_queue_time;
int var_dsn_queue_time;
int var_qmgr_active_limit;
int var_qmgr_rcpt_limit;
int var_qmgr_msg_rcpt_limit;
int var_xport_rcpt_limit;
int var_stack_rcpt_limit;
int var_delivery_slot_cost;
int var_delivery_slot_loan;
int var_delivery_slot_discount;
int var_min_delivery_slots;
int var_init_dest_concurrency;
int var_transport_retry_time;
int var_dest_con_limit;
int var_dest_rcpt_limit;
char *var_defer_xports;
bool var_allow_min_user;
int var_qmgr_fudge;
int var_local_rcpt_lim; /* XXX */
int var_local_con_lim; /* XXX */
int var_local_con_lim;
int var_local_rcpt_lim;
int var_proc_limit;
bool var_verp_bounce_off;
bool var_sender_routing;
@@ -392,16 +443,13 @@ static int qmgr_loop(char *unused_name, char **unused_argv)
/*
* Let some new blood into the active queue when the queue size is
* smaller than some configurable limit, and when the number of in-core
* recipients does not exceed some configurable limit. When the system is
* under heavy load, favor new mail over old mail.
* smaller than some configurable limit. When the system is under heavy
* load, favor new mail over old mail.
*/
if (qmgr_message_count < var_qmgr_active_limit
&& qmgr_recipient_count < var_qmgr_rcpt_limit)
if (qmgr_message_count < var_qmgr_active_limit)
if ((in_path = qmgr_scan_next(qmgr_incoming)) != 0)
in_feed = qmgr_active_feed(qmgr_incoming, in_path);
if (qmgr_message_count < var_qmgr_active_limit
&& qmgr_recipient_count < var_qmgr_rcpt_limit)
if (qmgr_message_count < var_qmgr_active_limit)
if ((df_path = qmgr_scan_next(qmgr_deferred)) != 0)
qmgr_active_feed(qmgr_deferred, df_path);
@@ -439,9 +487,19 @@ static void pre_accept(char *unused_name, char **unused_argv)
/* qmgr_post_init - post-jail initialization */
static void qmgr_post_init(char *unused_name, char **unused_argv)
static void qmgr_post_init(char *name, char **unused_argv)
{
/*
* Backwards compatibility.
*/
if (strcmp(var_procname, "nqmgr") == 0) {
msg_warn("please update the %s/%s file; the new queue manager",
var_config_dir, MASTER_CONF_FILE);
msg_warn("(old name: nqmgr) has become the standard queue manager (new name: qmgr)");
msg_warn("support for the name old name (nqmgr) will be removed from Postfix");
}
/*
* Sanity check.
*/
@@ -493,10 +551,16 @@ int main(int argc, char **argv)
static CONFIG_INT_TABLE int_table[] = {
VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0,
VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0,
VAR_QMGR_MSG_RCPT_LIMIT, DEF_QMGR_MSG_RCPT_LIMIT, &var_qmgr_msg_rcpt_limit, 1, 0,
VAR_XPORT_RCPT_LIMIT, DEF_XPORT_RCPT_LIMIT, &var_xport_rcpt_limit, 0, 0,
VAR_STACK_RCPT_LIMIT, DEF_STACK_RCPT_LIMIT, &var_stack_rcpt_limit, 0, 0,
VAR_DELIVERY_SLOT_COST, DEF_DELIVERY_SLOT_COST, &var_delivery_slot_cost, 0, 0,
VAR_DELIVERY_SLOT_LOAN, DEF_DELIVERY_SLOT_LOAN, &var_delivery_slot_loan, 0, 0,
VAR_DELIVERY_SLOT_DISCOUNT, DEF_DELIVERY_SLOT_DISCOUNT, &var_delivery_slot_discount, 0, 100,
VAR_MIN_DELIVERY_SLOTS, DEF_MIN_DELIVERY_SLOTS, &var_min_delivery_slots, 0, 0,
VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0,
VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0,
VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0,
VAR_QMGR_FUDGE, DEF_QMGR_FUDGE, &var_qmgr_fudge, 10, 100,
VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0,
VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0,
VAR_PROC_LIMIT, DEF_PROC_LIMIT, &var_proc_limit, 1, 0,

View File

@@ -22,9 +22,13 @@ typedef struct QMGR_TRANSPORT QMGR_TRANSPORT;
typedef struct QMGR_QUEUE QMGR_QUEUE;
typedef struct QMGR_ENTRY QMGR_ENTRY;
typedef struct QMGR_MESSAGE QMGR_MESSAGE;
typedef struct QMGR_JOB QMGR_JOB;
typedef struct QMGR_PEER QMGR_PEER;
typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST;
typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST;
typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST;
typedef struct QMGR_JOB_LIST QMGR_JOB_LIST;
typedef struct QMGR_PEER_LIST QMGR_PEER_LIST;
typedef struct QMGR_RCPT QMGR_RCPT;
typedef struct QMGR_RCPT_LIST QMGR_RCPT_LIST;
typedef struct QMGR_SCAN QMGR_SCAN;
@@ -32,17 +36,16 @@ typedef struct QMGR_SCAN QMGR_SCAN;
/*
* Hairy macros to update doubly-linked lists.
*/
#define QMGR_LIST_ROTATE(head, object) { \
#define QMGR_LIST_ROTATE(head, object, peers) { \
head.next->peers.prev = head.prev; \
head.prev->peers.next = head.next; \
head.next = object->peers.next; \
if (object->peers.next) \
head.next->peers.prev = 0; \
head.prev = object; \
object->peers.next = 0; \
}
#define QMGR_LIST_UNLINK(head, type, object) { \
#define QMGR_LIST_UNLINK(head, type, object, peers) { \
type next = object->peers.next; \
type prev = object->peers.prev; \
if (prev) prev->peers.next = next; \
@@ -52,7 +55,16 @@ typedef struct QMGR_SCAN QMGR_SCAN;
object->peers.next = object->peers.prev = 0; \
}
#define QMGR_LIST_APPEND(head, object) { \
#define QMGR_LIST_LINK(head, pred, object, succ, peers) { \
object->peers.prev = pred; \
object->peers.next = succ; \
if (pred) pred->peers.next = object; \
else head.next = object; \
if (succ) succ->peers.prev = object; \
else head.prev = object; \
}
#define QMGR_LIST_PREPEND(head, object, peers) { \
object->peers.next = head.next; \
object->peers.prev = 0; \
if (head.next) { \
@@ -63,7 +75,7 @@ typedef struct QMGR_SCAN QMGR_SCAN;
head.next = object; \
}
#define QMGR_LIST_PREPEND(head, object) { \
#define QMGR_LIST_APPEND(head, object, peers) { \
object->peers.prev = head.prev; \
object->peers.next = 0; \
if (head.prev) { \
@@ -102,14 +114,40 @@ struct QMGR_QUEUE_LIST {
QMGR_QUEUE *prev;
};
struct QMGR_JOB_LIST {
QMGR_JOB *next;
QMGR_JOB *prev;
};
struct QMGR_TRANSPORT {
int flags; /* blocked, etc. */
char *name; /* transport name */
int dest_concurrency_limit; /* concurrency per domain */
int init_dest_concurrency; /* init. per-domain concurrency */
int recipient_limit; /* recipients per transaction */
int rcpt_per_stack; /* extra slots reserved for jobs put
* on the job stack */
int rcpt_unused; /* available in-core recipient slots */
int slot_cost; /* cost of new preemption slot (# of
* selected entries) */
int slot_loan; /* preemption boost offset and */
int slot_loan_factor; /* factor, see qmgr_job_preempt() */
int min_slots; /* when preemption can take effect at
* all */
struct HTABLE *queue_byname; /* queues indexed by domain */
QMGR_QUEUE_LIST queue_list; /* queues, round robin order */
struct HTABLE *job_byname; /* jobs indexed by queue id */
QMGR_JOB_LIST job_list; /* list of message jobs (1 per
* message) ordered by scheduler */
QMGR_JOB_LIST job_bytime; /* jobs ordered by time since queued */
QMGR_JOB *job_current; /* keeps track of the current job */
QMGR_JOB *job_next_unread; /* next job with unread recipients */
QMGR_JOB *candidate_cache; /* cached result from
* qmgr_job_candidate() */
QMGR_JOB *candidate_cache_current; /* current job tied to the candidate */
time_t candidate_cache_time; /* when candidate_cache was last
* updated */
int blocker_tag; /* for marking blocker jobs */
QMGR_TRANSPORT_LIST peers; /* linkage */
char *reason; /* why unavailable */
};
@@ -130,9 +168,7 @@ extern QMGR_TRANSPORT *qmgr_transport_find(const char *);
* transactions. The "todo" queue contains messages that are to be delivered
* to this next hop. When a message is elected for transmission, it is moved
* from the "todo" queue to the "busy" queue. Messages are taken from the
* "todo" queue in sequence. An initial destination delivery concurrency > 1
* ensures that one problematic message will not block all other traffic to
* that next hop.
* "todo" queue in round-robin order.
*/
struct QMGR_ENTRY_LIST {
QMGR_ENTRY *next;
@@ -150,7 +186,8 @@ struct QMGR_QUEUE {
QMGR_ENTRY_LIST busy; /* messages on the wire */
QMGR_QUEUE_LIST peers; /* neighbor queues */
char *reason; /* why unavailable */
time_t clog_time_to_warn; /* time of next warning */
time_t clog_time_to_warn; /* time of last warning */
int blocker_tag; /* tagged if blocks job list */
};
#define QMGR_QUEUE_TODO 1 /* waiting for service */
@@ -159,7 +196,6 @@ struct QMGR_QUEUE {
extern int qmgr_queue_count;
extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *);
extern QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *);
extern void qmgr_queue_done(QMGR_QUEUE *);
extern void qmgr_queue_throttle(QMGR_QUEUE *, const char *);
extern void qmgr_queue_unthrottle(QMGR_QUEUE *);
@@ -198,13 +234,15 @@ struct QMGR_ENTRY {
QMGR_MESSAGE *message; /* message info */
QMGR_RCPT_LIST rcpt_list; /* as many as it takes */
QMGR_QUEUE *queue; /* parent linkage */
QMGR_ENTRY_LIST peers; /* neighbor entries */
QMGR_PEER *peer; /* parent linkage */
QMGR_ENTRY_LIST queue_peers; /* per queue neighbor entries */
QMGR_ENTRY_LIST peer_peers; /* per peer neighbor entries */
};
extern QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *);
extern void qmgr_entry_unselect(QMGR_QUEUE *, QMGR_ENTRY *);
extern QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *);
extern void qmgr_entry_unselect(QMGR_ENTRY *);
extern void qmgr_entry_done(QMGR_ENTRY *, int);
extern QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *, QMGR_MESSAGE *);
extern QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *, QMGR_MESSAGE *);
/*
* All common in-core information about a message is kept here. When all
@@ -221,6 +259,8 @@ struct QMGR_MESSAGE {
int refcount; /* queue entries */
int single_rcpt; /* send one rcpt at a time */
long arrival_time; /* time when queued */
time_t queued_time; /* time when moved to the active
* queue */
long warn_offset; /* warning bounce flag offset */
time_t warn_time; /* time next warning to be sent */
long data_offset; /* data seek offset */
@@ -241,6 +281,11 @@ struct QMGR_MESSAGE {
char *client_proto; /* client protocol */
char *client_helo; /* helo parameter */
QMGR_RCPT_LIST rcpt_list; /* complete addresses */
int rcpt_count; /* used recipient slots */
int rcpt_limit; /* maximum read in-core */
int rcpt_unread; /* # of recipients left in queue file */
QMGR_JOB_LIST job_list; /* jobs delivering this message (1
* per transport) */
};
/*
@@ -258,6 +303,63 @@ extern void qmgr_message_update_warn(QMGR_MESSAGE *);
extern QMGR_MESSAGE *qmgr_message_alloc(const char *, const char *, int);
extern QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *);
/*
* Sometimes it's required to access the transport queues and entries on per
* message basis. That's what the QMGR_JOB structure is for - it groups all
* per message information within each transport using a list of QMGR_PEER
* structures. These structures in turn correspond with per message
* QMGR_QUEUE structure and list all per message QMGR_ENTRY structures.
*/
struct QMGR_PEER_LIST {
QMGR_PEER *next;
QMGR_PEER *prev;
};
struct QMGR_JOB {
QMGR_MESSAGE *message; /* message delivered by this job */
QMGR_TRANSPORT *transport; /* transport this job belongs to */
QMGR_JOB_LIST message_peers; /* per message neighbor linkage */
QMGR_JOB_LIST transport_peers; /* per transport neighbor linkage */
QMGR_JOB_LIST time_peers; /* by time neighbor linkage */
QMGR_JOB *stack_parent; /* stack parent */
QMGR_JOB_LIST stack_children; /* all stack children */
QMGR_JOB_LIST stack_siblings; /* stack children linkage */
int stack_level; /* job stack nesting level (-1 means
* it's not on the lists at all) */
int blocker_tag; /* tagged if blocks the job list */
struct HTABLE *peer_byname; /* message job peers, indexed by
* domain */
QMGR_PEER_LIST peer_list; /* list of message job peers */
int slots_used; /* slots used during preemption */
int slots_available; /* slots available for preemption (in
* multiples of slot_cost) */
int selected_entries; /* # of entries selected for delivery
* so far */
int read_entries; /* # of entries read in-core so far */
int rcpt_count; /* used recipient slots */
int rcpt_limit; /* available recipient slots */
};
struct QMGR_PEER {
QMGR_JOB *job; /* job handling this peer */
QMGR_QUEUE *queue; /* queue corresponding with this peer */
int refcount; /* peer entries */
QMGR_ENTRY_LIST entry_list; /* todo message entries queued for
* this peer */
QMGR_PEER_LIST peers; /* neighbor linkage */
};
extern QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *);
extern QMGR_PEER *qmgr_peer_select(QMGR_JOB *);
extern QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *, QMGR_TRANSPORT *);
extern void qmgr_job_free(QMGR_JOB *);
extern void qmgr_job_move_limits(QMGR_JOB *);
extern QMGR_PEER *qmgr_peer_create(QMGR_JOB *, QMGR_QUEUE *);
extern QMGR_PEER *qmgr_peer_find(QMGR_JOB *, QMGR_QUEUE *);
extern void qmgr_peer_free(QMGR_PEER *);
/*
* qmgr_defer.c
*/
@@ -329,4 +431,9 @@ extern char *qmgr_scan_next(QMGR_SCAN *);
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/

View File

@@ -35,6 +35,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */

View File

@@ -60,6 +60,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -127,7 +132,7 @@ void qmgr_defer_todo(QMGR_QUEUE *queue, const char *reason)
* Proceed carefully. Queue entries will disappear as a side effect.
*/
for (entry = queue->todo.next; entry != 0; entry = next) {
next = entry->peers.next;
next = entry->queue_peers.next;
message = entry->message;
for (nrcpt = 0; nrcpt < entry->rcpt_list.len; nrcpt++) {
recipient = entry->rcpt_list.info + nrcpt;

View File

@@ -39,6 +39,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -282,7 +287,6 @@ static void qmgr_deliver_update(int unused_event, char *context)
void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
{
QMGR_QUEUE *queue;
QMGR_ENTRY *entry;
/*
@@ -306,8 +310,7 @@ void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
* agent request reading routine is prepared for the queue manager to
* change its mind for no apparent reason.
*/
if ((queue = qmgr_queue_select(transport)) == 0
|| (entry = qmgr_entry_select(queue)) == 0) {
if ((entry = qmgr_job_entry_select(transport)) == 0) {
(void) vstream_fclose(stream);
return;
}
@@ -319,10 +322,10 @@ void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream)
* while some other queue manipulation is happening.
*/
if (qmgr_deliver_send_request(entry, stream) < 0) {
qmgr_entry_unselect(queue, entry);
qmgr_entry_unselect(entry);
qmgr_transport_throttle(transport, "mail transport unavailable");
qmgr_defer_transport(transport, transport->reason);
/* warning: entry and queue may be dangling pointers here */
/* warning: entry may be a dangling pointer here */
(void) vstream_fclose(stream);
return;
}

View File

@@ -6,8 +6,8 @@
/* SYNOPSIS
/* #include "qmgr.h"
/*
/* QMGR_ENTRY *qmgr_entry_create(queue, message)
/* QMGR_QUEUE *queue;
/* QMGR_ENTRY *qmgr_entry_create(peer, message)
/* QMGR_PEER *peer;
/* QMGR_MESSAGE *message;
/*
/* void qmgr_entry_done(entry, which)
@@ -24,8 +24,8 @@
/* These routines add/delete/manipulate per-site message
/* delivery requests.
/*
/* qmgr_entry_create() creates an entry for the named queue and
/* message, and appends the entry to the queue's todo list.
/* qmgr_entry_create() creates an entry for the named peer and message,
/* and appends the entry to the peer's list and its queue's todo list.
/* Filling in and cleaning up the recipients is the responsibility
/* of the caller.
/*
@@ -36,6 +36,9 @@
/* of the site's `todo' list (i.e. queue entries awaiting selection
/* for actual delivery).
/*
/* qmgr_entry_done() discards its peer structure when the peer
/* is not referenced anymore.
/*
/* qmgr_entry_done() triggers cleanup of the per-site queue when
/* the site has no pending deliveries, and the site is either
/* alive, or the site is dead and the number of in-core queues
@@ -47,14 +50,14 @@
/* the queue file to the deferred queue; send bounce reports to the
/* message originator (see qmgr_active_done()).
/*
/* qmgr_entry_select() selects the next entry from the named
/* qmgr_entry_select() selects first entry from the named
/* per-site queue's `todo' list for actual delivery. The entry is
/* moved to the queue's `busy' list: the list of messages being
/* delivered.
/* delivered. The entry is also removed from its peer list.
/*
/* qmgr_entry_unselect() takes the named entry off the named
/* per-site queue's `busy' list and moves it to the queue's
/* `todo' list.
/* `todo' list. The entry is also appended to its peer list again.
/* DIAGNOSTICS
/* Panic: interface violations, internal inconsistencies.
/* LICENSE
@@ -66,6 +69,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -91,27 +99,36 @@
/* qmgr_entry_select - select queue entry for delivery */
QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *queue)
QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *peer)
{
QMGR_ENTRY *entry;
QMGR_QUEUE *queue;
if ((entry = queue->todo.prev) != 0) {
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry);
if ((entry = peer->entry_list.next) != 0) {
queue = entry->queue;
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
queue->todo_refcount--;
QMGR_LIST_APPEND(queue->busy, entry);
QMGR_LIST_APPEND(queue->busy, entry, queue_peers);
queue->busy_refcount++;
QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
peer->job->selected_entries++;
}
return (entry);
}
/* qmgr_entry_unselect - unselect queue entry for delivery */
void qmgr_entry_unselect(QMGR_QUEUE *queue, QMGR_ENTRY *entry)
void qmgr_entry_unselect(QMGR_ENTRY *entry)
{
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry);
QMGR_PEER *peer = entry->peer;
QMGR_QUEUE *queue = entry->queue;
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers);
queue->busy_refcount--;
QMGR_LIST_APPEND(queue->todo, entry);
QMGR_LIST_APPEND(queue->todo, entry, queue_peers);
queue->todo_refcount++;
QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers);
peer->job->selected_entries--;
}
/* qmgr_entry_done - dispose of queue entry */
@@ -120,6 +137,10 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which)
{
QMGR_QUEUE *queue = entry->queue;
QMGR_MESSAGE *message = entry->message;
QMGR_PEER *peer = entry->peer;
QMGR_JOB *sponsor,
*job = peer->job;
QMGR_TRANSPORT *transport = job->transport;
/*
* Take this entry off the in-core queue.
@@ -127,24 +148,76 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which)
if (entry->stream != 0)
msg_panic("qmgr_entry_done: file is open");
if (which == QMGR_QUEUE_BUSY) {
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry);
QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers);
queue->busy_refcount--;
} else if (which == QMGR_QUEUE_TODO) {
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry);
QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
job->selected_entries++;
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
queue->todo_refcount--;
} else {
msg_panic("qmgr_entry_done: bad queue spec: %d", which);
}
/*
* Free the recipient list and decrease the in-core recipient count
* accordingly.
* Decrease the in-core recipient counts and free the recipient list and
* the structure itself.
*/
job->rcpt_count -= entry->rcpt_list.len;
message->rcpt_count -= entry->rcpt_list.len;
qmgr_recipient_count -= entry->rcpt_list.len;
qmgr_rcpt_list_free(&entry->rcpt_list);
myfree((char *) entry);
/*
* Make sure that the transport of any retired or finishing job that
* donated recipient slots to this message gets them back first. Then, if
* possible, pass the remaining unused recipient slots to the next job on
* the job list.
*/
for (sponsor = message->job_list.next; sponsor; sponsor = sponsor->message_peers.next) {
if (sponsor->rcpt_count >= sponsor->rcpt_limit || sponsor == job)
continue;
if (sponsor->stack_level < 0 || message->rcpt_offset == 0)
qmgr_job_move_limits(sponsor);
}
if (message->rcpt_offset == 0) {
qmgr_job_move_limits(job);
}
/*
* If the queue was blocking some of the jobs on the job list, check if
* the concurrency limit has lifted. If there are still some pending
* deliveries, give it a try and unmark all transport blockers at once.
* The qmgr_job_entry_select() will do the rest. In either case make sure
* the queue is not marked as a blocker anymore, with extra handling of
* queues which were declared dead.
*
* Note that changing the blocker status also affects the candidate cache.
* Most of the cases would be automatically recognized by the current job
* change, but we play safe and reset the cache explicitly below.
*
* Keeping the transport blocker tag odd is an easy way to make sure the tag
* never matches jobs that are not explicitly marked as blockers.
*/
if (queue->blocker_tag == transport->blocker_tag) {
if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
transport->blocker_tag += 2;
transport->job_current = transport->job_list.next;
transport->candidate_cache_current = 0;
}
if (queue->window > queue->busy_refcount || queue->window == 0)
queue->blocker_tag = 0;
}
/*
* When there are no more entries for this peer, discard the peer
* structure.
*/
peer->refcount--;
if (peer->refcount == 0)
qmgr_peer_free(peer);
/*
* When the in-core queue for this site is empty and when this site is
* not dead, discard the in-core queue. When this site is dead, but the
@@ -161,33 +234,18 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which)
/*
* Update the in-core message reference count. When the in-core message
* structure has no more references, dispose of the message.
*
* When the in-core recipient count falls below a threshold, and this
* message has more recipients, read more recipients now. If we read more
* recipients as soon as the recipient count falls below the in-core
* recipient limit, we do not give other messages a chance until this
* message is delivered. That's good for mailing list deliveries, bad for
* one-to-one mail. If we wait until the in-core recipient count drops
* well below the in-core recipient limit, we give other mail a chance,
* but we also allow list deliveries to become interleaved. In the worst
* case, people near the start of a mailing list get a burst of postings
* today, while people near the end of the list get that same burst of
* postings a whole day later.
*/
#define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0))
message->refcount--;
if (message->rcpt_offset > 0
&& qmgr_recipient_count < FUDGE(var_qmgr_rcpt_limit))
qmgr_message_realloc(message);
if (message->refcount == 0)
qmgr_active_done(message);
}
/* qmgr_entry_create - create queue todo entry */
QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message)
QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message)
{
QMGR_ENTRY *entry;
QMGR_QUEUE *queue = peer->queue;
/*
* Sanity check.
@@ -203,8 +261,11 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message)
entry->message = message;
qmgr_rcpt_list_init(&entry->rcpt_list);
message->refcount++;
entry->peer = peer;
QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers);
peer->refcount++;
entry->queue = queue;
QMGR_LIST_APPEND(queue->todo, entry);
QMGR_LIST_APPEND(queue->todo, entry, queue_peers);
queue->todo_refcount++;
/*

View File

@@ -49,7 +49,10 @@
/* qmgr_message_realloc() resumes reading recipients from the queue
/* file, and updates the recipient list and \fIrcpt_offset\fR message
/* structure members. A null result means that the file could not be
/* read or that the file contained incorrect information.
/* read or that the file contained incorrect information. Recipient
/* limit imposed this time is based on the position of the message
/* job(s) on corresponding transport job list(s). It's considered
/* an error to call this when the recipient slots can't be allocated.
/*
/* qmgr_message_free() destroys an in-core message structure and makes
/* the resources available for reuse. It is an error to destroy
@@ -70,6 +73,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -98,6 +106,7 @@
#include <argv.h>
#include <stringops.h>
#include <myflock.h>
#include <sane_time.h>
/* Global library. */
@@ -143,6 +152,7 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name,
message->refcount = 0;
message->single_rcpt = 0;
message->arrival_time = 0;
message->queued_time = sane_time();
message->data_offset = 0;
message->queue_id = mystrdup(queue_id);
message->queue_name = mystrdup(queue_name);
@@ -163,6 +173,10 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name,
message->client_proto = 0;
message->client_helo = 0;
qmgr_rcpt_list_init(&message->rcpt_list);
message->rcpt_count = 0;
message->rcpt_limit = var_qmgr_msg_rcpt_limit;
message->rcpt_unread = 0;
QMGR_LIST_INIT(message->job_list);
return (message);
}
@@ -230,6 +244,7 @@ static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
* so we set data_size to this as well and ignore the size record itself
* completely.
*/
message->rcpt_unread = 0;
for (;;) {
rec_type = rec_get(message->fp, buf, 0);
if (rec_type <= 0)
@@ -240,6 +255,10 @@ static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
msg_info("old-style scan record %c %s", rec_type, start);
if (rec_type == REC_TYPE_END)
break;
if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_RCPT) {
message->rcpt_unread++;
continue;
}
if (rec_type == REC_TYPE_MESG) {
if (message->data_offset == 0) {
if ((message->data_offset = vstream_ftell(message->fp)) < 0)
@@ -278,8 +297,9 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
int rec_type;
long curr_offset;
long save_offset = message->rcpt_offset; /* save a flag */
int save_unread = message->rcpt_unread; /* save a count */
char *start;
int nrcpt = 0;
int recipient_limit;
const char *error_text;
char *name;
char *value;
@@ -294,6 +314,15 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
/*
* If we re-open this file, skip over on-file recipient records that we
* already looked at, and refill the in-core recipient address list.
*
* For the first time, the message recipient limit is calculated from the
* global recipient limit. This is to avoid reading little recipients
* when the active queue is near empty. When the queue becomes full, only
* the necessary amount is read in core. Such priming is necessary
* because there are no message jobs yet.
*
* For the next time, the recipient limit is based solely on the message
* jobs' positions in the job lists and/or job stacks.
*/
if (message->rcpt_offset) {
if (message->rcpt_list.len)
@@ -302,26 +331,29 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0)
msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
message->rcpt_offset = 0;
recipient_limit = message->rcpt_limit - message->rcpt_count;
} else {
recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count;
if (recipient_limit < message->rcpt_limit)
recipient_limit = message->rcpt_limit;
}
if (recipient_limit <= 0)
msg_panic("%s: no recipient slots available", message->queue_id);
/*
* Read envelope records. XXX Rely on the front-end programs to enforce
* record size limits. Read up to var_qmgr_rcpt_limit recipients from the
* record size limits. Read up to recipient_limit recipients from the
* queue file, to protect against memory exhaustion. Recipient records
* may appear before or after the message content, so we keep reading
* from the queue file until we have enough recipients (rcpt_offset != 0)
* and until we know all the non-recipient information.
*
* When reading recipients from queue file, stop reading when we reach a
* per-message in-core recipient limit rather than a global in-core
* recipient limit. Use the global recipient limit only in order to stop
* opening queue files. The purpose is to achieve equal delay for
* messages with recipient counts up to var_qmgr_rcpt_limit recipients.
*
* If we would read recipients up to a global recipient limit, the average
* number of in-core recipients per message would asymptotically approach
* (global recipient limit)/(active queue size limit), which gives equal
* delay per recipient rather than equal delay per message.
* Note that the total recipient count record is accurate only for fresh
* queue files. After some of the recipients are marked as done and the
* queue file is deferred, it can be used as upper bound estimate only.
* Fortunately, this poses no major problem on the scheduling algorithm,
* as the only impact is that the already deferred messages are not
* chosen by qmgr_job_candidate() as often as they could.
*
* On the first open, we must examine all non-recipient records.
*
@@ -358,15 +390,15 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
*/
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_offset == 0) {
message->rcpt_unread--;
qmgr_rcpt_list_add(&message->rcpt_list, curr_offset,
orig_rcpt ? orig_rcpt : "", start);
if (orig_rcpt) {
myfree(orig_rcpt);
orig_rcpt = 0;
}
if (message->rcpt_list.len >= FUDGE(var_qmgr_rcpt_limit)) {
if (message->rcpt_list.len >= recipient_limit) {
if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
msg_fatal("vstream_ftell %s: %m",
VSTREAM_PATH(message->fp));
@@ -392,10 +424,13 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
continue;
}
if (rec_type == REC_TYPE_DONE) {
if (message->rcpt_offset == 0) {
message->rcpt_unread--;
if (orig_rcpt) {
myfree(orig_rcpt);
orig_rcpt = 0;
}
}
continue;
}
if (orig_rcpt != 0) {
@@ -422,7 +457,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
if (message->data_offset == 0) {
if ((count = sscanf(start, "%ld %ld %d %d",
&message->data_size, &message->data_offset,
&nrcpt, &message->rflags)) >= 3) {
&message->rcpt_unread, &message->rflags)) >= 3) {
/* Postfix >= 1.0 (a.k.a. 20010228). */
if (message->data_offset <= 0 || message->data_size <= 0) {
msg_warn("%s: invalid size record: %.100s",
@@ -476,7 +511,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
if (message->sender == 0) {
message->sender = mystrdup(start);
opened(message->queue_id, message->sender,
message->data_size, nrcpt,
message->data_size, message->rcpt_unread,
"queue %s", message->queue_name);
}
continue;
@@ -594,6 +629,12 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
* Sanity checks. Verify that all required information was found,
* including the queue file end marker.
*/
if (message->rcpt_unread < 0
|| (message->rcpt_offset == 0 && message->rcpt_unread != 0)) {
msg_warn("%s: rcpt count mismatch (%d)",
message->queue_id, message->rcpt_unread);
message->rcpt_unread = 0;
}
if (rec_type <= 0) {
/* Already logged warning. */
} else if (message->arrival_time == 0) {
@@ -609,6 +650,9 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
return (0);
}
message->rcpt_offset = save_offset; /* restore flag */
message->rcpt_unread = save_unread; /* restore count */
qmgr_rcpt_list_free(&message->rcpt_list);
qmgr_rcpt_list_init(&message->rcpt_list);
return (-1);
}
@@ -685,7 +729,7 @@ static int qmgr_message_sort_compare(const void *p1, const void *p2)
/*
* Compare recipient address.
*/
return (strcasecmp(rcpt1->address, rcpt2->address));
return (strcmp(rcpt1->address, rcpt2->address));
}
/* qmgr_message_sort - sort message recipient addresses by domain */
@@ -970,6 +1014,8 @@ static void qmgr_message_assign(QMGR_MESSAGE *message)
QMGR_RCPT *recipient;
QMGR_ENTRY *entry = 0;
QMGR_QUEUE *queue;
QMGR_JOB *job = 0;
QMGR_PEER *peer = 0;
/*
* Try to bundle as many recipients in a delivery request as we can. When
@@ -985,27 +1031,87 @@ static void qmgr_message_assign(QMGR_MESSAGE *message)
for (recipient = list.info; recipient < list.info + list.len; recipient++) {
if ((queue = recipient->queue) != 0) {
if (message->single_rcpt || entry == 0 || entry->queue != queue
|| !LIMIT_OK(entry->queue->transport->recipient_limit,
|| !LIMIT_OK(queue->transport->recipient_limit,
entry->rcpt_list.len)) {
entry = qmgr_entry_create(queue, message);
/*
* Lookup or instantiate the message job if necessary.
*/
if (job == 0 || queue->transport != job->transport) {
job = qmgr_job_obtain(message, queue->transport);
peer = 0;
}
/*
* Lookup or instantiate job peer if necessary.
*/
if (peer == 0 || queue != peer->queue) {
if ((peer = qmgr_peer_find(job, queue)) == 0)
peer = qmgr_peer_create(job, queue);
}
/*
* Create new peer entry.
*/
entry = qmgr_entry_create(peer, message);
job->read_entries++;
}
/*
* Add the recipient to the current entry and increase all those
* recipient counters accordingly.
*/
qmgr_rcpt_list_add(&entry->rcpt_list, recipient->offset,
recipient->orig_rcpt, recipient->address);
job->rcpt_count++;
message->rcpt_count++;
qmgr_recipient_count++;
}
}
/*
* Release the message recipient list and reinitialize it for the next
* time.
*/
qmgr_rcpt_list_free(&message->rcpt_list);
qmgr_rcpt_list_init(&message->rcpt_list);
/*
* Note that even if qmgr_job_obtain() reset the job candidate cache of
* all transports to which we assigned new recipients, this message may
* have other jobs which we didn't touch at all this time. But the number
* of unread recipients affecting the candidate selection might have
* changed considerably, so we must invalidate the caches if it might be
* of some use.
*/
for (job = message->job_list.next; job; job = job->message_peers.next)
if (job->selected_entries < job->read_entries
&& job->blocker_tag != job->transport->blocker_tag)
job->transport->candidate_cache_current = 0;
}
/* qmgr_message_move_limits - recycle unused recipient slots */
static void qmgr_message_move_limits(QMGR_MESSAGE *message)
{
QMGR_JOB *job;
for (job = message->job_list.next; job; job = job->message_peers.next)
qmgr_job_move_limits(job);
}
/* qmgr_message_free - release memory for in-core message structure */
void qmgr_message_free(QMGR_MESSAGE *message)
{
QMGR_JOB *job;
if (message->refcount != 0)
msg_panic("qmgr_message_free: reference len: %d", message->refcount);
if (message->fp)
msg_panic("qmgr_message_free: queue file is open");
while ((job = message->job_list.next) != 0)
qmgr_job_free(job);
myfree(message->queue_id);
myfree(message->queue_name);
if (message->encoding)
@@ -1090,6 +1196,8 @@ QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id,
qmgr_message_sort(message);
qmgr_message_assign(message);
qmgr_message_close(message);
if (message->rcpt_offset == 0)
qmgr_message_move_limits(message);
return (message);
}
}
@@ -1124,6 +1232,8 @@ QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *message)
qmgr_message_sort(message);
qmgr_message_assign(message);
qmgr_message_close(message);
if (message->rcpt_offset == 0)
qmgr_message_move_limits(message);
return (message);
}
}

View File

@@ -20,9 +20,6 @@
/* QMGR_TRANSPORT *transport;
/* const char *name;
/*
/* QMGR_QUEUE *qmgr_queue_select(transport)
/* QMGR_TRANSPORT *transport;
/*
/* void qmgr_queue_throttle(queue, reason)
/* QMGR_QUEUE *queue;
/* const char *reason;
@@ -52,10 +49,6 @@
/* qmgr_queue_find() looks up the named queue for the named
/* transport. A null result means that the queue was not found.
/*
/* qmgr_queue_select() uses a round-robin strategy to select
/* from the named transport one per-destination queue with a
/* non-empty `todo' list.
/*
/* qmgr_queue_throttle() handles a delivery error, and decrements the
/* concurrency limit for the destination. When the concurrency limit
/* for a destination becomes zero, qmgr_queue_throttle() starts a timer
@@ -67,7 +60,7 @@
/* limit specified for the transport. This routine implements
/* "slow open" mode, and eliminates the "thundering herd" problem.
/* DIAGNOSTICS
/* None
/* Panic: consistency check failure.
/* LICENSE
/* .ad
/* .fi
@@ -77,6 +70,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -148,7 +146,7 @@ void qmgr_queue_unthrottle(QMGR_QUEUE *queue)
*/
if (transport->dest_concurrency_limit == 0
|| transport->dest_concurrency_limit > queue->window)
if (queue->window <= queue->busy_refcount + transport->init_dest_concurrency)
if (queue->window < queue->busy_refcount + transport->init_dest_concurrency)
queue->window++;
}
@@ -186,27 +184,6 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, const char *reason)
}
}
/* qmgr_queue_select - select in-core queue for delivery */
QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *transport)
{
QMGR_QUEUE *queue;
/*
* If we find a suitable site, rotate the list to enforce round-robin
* selection. See similar selection code in qmgr_transport_select().
*/
for (queue = transport->queue_list.next; queue; queue = queue->peers.next) {
if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
QMGR_LIST_ROTATE(transport->queue_list, queue);
if (msg_verbose)
msg_info("qmgr_queue_select: %s", queue->name);
return (queue);
}
}
return (0);
}
/* qmgr_queue_done - delete in-core queue for site */
void qmgr_queue_done(QMGR_QUEUE *queue)
@@ -232,7 +209,7 @@ void qmgr_queue_done(QMGR_QUEUE *queue)
/*
* Clean up this in-core queue.
*/
QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue);
QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue, peers);
htable_delete(transport->queue_byname, queue->name, (void (*) (char *)) 0);
myfree(queue->name);
myfree(queue->nexthop);
@@ -264,7 +241,8 @@ QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name,
QMGR_LIST_INIT(queue->busy);
queue->reason = 0;
queue->clog_time_to_warn = 0;
QMGR_LIST_PREPEND(transport->queue_list, queue);
queue->blocker_tag = 0;
QMGR_LIST_APPEND(transport->queue_list, queue, peers);
htable_enter(transport->queue_byname, name, (char *) queue);
return (queue);
}

View File

@@ -64,6 +64,11 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Scheduler enhancements:
/* Patrik Rak
/* Modra 6
/* 155 00, Prague, Czech Republic
/*--*/
/* System library. */
@@ -233,7 +238,7 @@ QMGR_TRANSPORT *qmgr_transport_select(void)
/*
* If we find a suitable transport, rotate the list of transports to
* effectuate round-robin selection. See similar selection code in
* qmgr_queue_select().
* qmgr_peer_select().
*/
#define STAY_AWAY (QMGR_TRANSPORT_STAT_BUSY | QMGR_TRANSPORT_STAT_DEAD)
@@ -242,7 +247,7 @@ QMGR_TRANSPORT *qmgr_transport_select(void)
continue;
for (queue = xport->queue_list.next; queue; queue = queue->peers.next) {
if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
QMGR_LIST_ROTATE(qmgr_transport_list, xport);
QMGR_LIST_ROTATE(qmgr_transport_list, xport, peers);
if (msg_verbose)
msg_info("qmgr_transport_select: %s", xport->name);
return (xport);
@@ -319,10 +324,10 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name)
* Use global configuration settings or transport-specific settings.
*/
transport->dest_concurrency_limit =
get_mail_conf_int2(name, "_destination_concurrency_limit",
get_mail_conf_int2(name, _DEST_CON_LIMIT,
var_dest_con_limit, 0, 0);
transport->recipient_limit =
get_mail_conf_int2(name, "_destination_recipient_limit",
get_mail_conf_int2(name, _DEST_RCPT_LIMIT,
var_dest_rcpt_limit, 0, 0);
if (transport->dest_concurrency_limit == 0
@@ -331,13 +336,36 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name)
else
transport->init_dest_concurrency = transport->dest_concurrency_limit;
transport->slot_cost = get_mail_conf_int2(name, _DELIVERY_SLOT_COST,
var_delivery_slot_cost, 0, 0);
transport->slot_loan = get_mail_conf_int2(name, _DELIVERY_SLOT_LOAN,
var_delivery_slot_loan, 0, 0);
transport->slot_loan_factor =
100 - get_mail_conf_int2(name, _DELIVERY_SLOT_DISCOUNT,
var_delivery_slot_discount, 0, 100);
transport->min_slots = get_mail_conf_int2(name, _MIN_DELIVERY_SLOTS,
var_min_delivery_slots, 0, 0);
transport->rcpt_unused = get_mail_conf_int2(name, _XPORT_RCPT_LIMIT,
var_xport_rcpt_limit, 0, 0);
transport->rcpt_per_stack = get_mail_conf_int2(name, _STACK_RCPT_LIMIT,
var_stack_rcpt_limit, 0, 0);
transport->queue_byname = htable_create(0);
QMGR_LIST_INIT(transport->queue_list);
transport->job_byname = htable_create(0);
QMGR_LIST_INIT(transport->job_list);
QMGR_LIST_INIT(transport->job_bytime);
transport->job_current = 0;
transport->job_next_unread = 0;
transport->candidate_cache = 0;
transport->candidate_cache_current = 0;
transport->candidate_cache_time = (time_t) 0;
transport->blocker_tag = 1;
transport->reason = 0;
if (qmgr_transport_byname == 0)
qmgr_transport_byname = htable_create(10);
htable_enter(qmgr_transport_byname, name, (char *) transport);
QMGR_LIST_APPEND(qmgr_transport_list, transport);
QMGR_LIST_PREPEND(qmgr_transport_list, transport, peers);
if (msg_verbose)
msg_info("qmgr_transport_create: %s concurrency %d recipients %d",
transport->name, transport->dest_concurrency_limit,

View File

@@ -117,6 +117,11 @@
/* Enable per-session authentication as per RFC 2554 (SASL).
/* This functionality is available only when explicitly selected
/* at program build time and explicitly enabled at runtime.
/* .IP \fBsmtpd_sasl_application_name\fR
/* The application name used for SASL server initialization. This
/* controls the name of the SASL configuration file. The default
/* value is \fIsmtpd\fR, corresponding to a SASL configuration file
/* named \fIsmtpd.conf\fR.
/* .IP \fBsmtpd_sasl_local_domain\fR
/* The name of the local authentication realm.
/* .IP \fBsmtpd_sasl_security_options\fR
@@ -529,6 +534,7 @@ bool var_allow_untrust_route;
int var_smtpd_junk_cmd_limit;
bool var_smtpd_sasl_enable;
char *var_smtpd_sasl_opts;
char *var_smtpd_sasl_appname;
char *var_smtpd_sasl_realm;
char *var_smtpd_sasl_exceptions_networks;
char *var_filter_xport;
@@ -2497,6 +2503,7 @@ int main(int argc, char **argv)
VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts, 0, 0,
VAR_SMTPD_SASL_APPNAME, DEF_SMTPD_SASL_APPNAME, &var_smtpd_sasl_appname, 1, 0,
VAR_SMTPD_SASL_REALM, DEF_SMTPD_SASL_REALM, &var_smtpd_sasl_realm, 0, 0,
VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, DEF_SMTPD_SASL_EXCEPTIONS_NETWORKS, &var_smtpd_sasl_exceptions_networks, 0, 0,
VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,

View File

@@ -3577,8 +3577,8 @@ char *smtpd_check_mail(SMTPD_STATE *state, char *sender)
state->defer_if_permit_sender = state->defer_if_permit.active;
/*
* If the "check_recipient_maps" restriction was not applied, and if mail
* is not being rejected or discarded, validate the recipient here.
* If the "reject_unlisted_sender" restriction was not applied, and if
* mail is not being rejected or discarded, validate the sender here.
*/
if (status != SMTPD_CHECK_REJECT && state->sender_rcptmap_checked == 0
&& state->discard == 0 && *sender)
@@ -3676,8 +3676,8 @@ char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient)
"%s", STR(state->defer_if_permit.reason));
/*
* If the "check_recipient_maps" restriction was not applied, and if mail
* is not being rejected or discarded, validate the recipient here.
* If the "reject_unlisted_recipient" restriction was not applied, and if
* mail is not being rejected or discarded, validate the recipient here.
*/
if (status != SMTPD_CHECK_REJECT && state->recipient_rcptmap_checked == 0
&& state->discard == 0)
@@ -3837,8 +3837,7 @@ static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient,
STR(reply->nexthop)));
/*
* Reject mail to unknown addresses in local domains (domains that match
* $mydestination or $inet_interfaces).
* Search the recipient lookup tables of the respective address class.
*
* XXX Use the less expensive maps_find() (case is already folded) instead
* of the baroque mail_addr_find(). But then we have to strip the domain
@@ -3852,6 +3851,10 @@ static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient,
switch (reply->flags & RESOLVE_CLASS_MASK) {
/*
* Reject mail to unknown addresses in local domains (domains that
* match $mydestination or $inet_interfaces).
*/
case RESOLVE_CLASS_LOCAL:
if (*var_local_rcpt_maps
/* Generated by bounce, absorbed by qmgr. */
@@ -3896,14 +3899,12 @@ static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient,
reply_class, var_show_unk_rcpt_table ?
" in relay recipient table" : ""));
break;
}
/*
* Accept all other addresses - including addresses that passed the
* above tests because of some table lookup problem.
* Accept all other addresses - including addresses that passed the above
* tests because of some table lookup problem.
*/
default:
break;
}
return (0);
}
@@ -3945,7 +3946,8 @@ char *smtpd_check_size(SMTPD_STATE *state, off_t size)
|| BLOCKS(var_message_limit) >= fsbuf.block_free / 2) {
(void) smtpd_check_reject(state, MAIL_ERROR_RESOURCE,
"452 Insufficient system storage");
msg_warn("not enough free space in mail queue: %lu bytes",
msg_warn("not enough free space in mail queue: %lu bytes < "
"2*message size limit",
(unsigned long) fsbuf.block_free * fsbuf.block_size);
return (STR(error_text));
}

View File

@@ -211,7 +211,10 @@ void smtpd_sasl_initialize(void)
/*
* Initialize the library: load SASL plug-in routines, etc.
*/
if (sasl_server_init(callbacks, "smtpd") != SASL_OK)
if (msg_verbose)
msg_info("smtpd_sasl_initialize: SASL config file is %s.conf",
var_smtpd_sasl_appname);
if (sasl_server_init(callbacks, var_smtpd_sasl_appname) != SASL_OK)
msg_fatal("SASL per-process initialization failed");
}

View File

@@ -3,8 +3,8 @@ SRCS = alldig.c argv.c argv_split.c attr_print0.c attr_print64.c \
attr_scan0.c attr_scan64.c base64_code.c basename.c binhash.c \
chroot_uid.c clean_env.c close_on_exec.c concatenate.c ctable.c \
dict.c dict_alloc.c dict_db.c dict_dbm.c dict_debug.c dict_env.c \
dict_cidr.c dict_ht.c dict_ldap.c dict_mysql.c dict_ni.c dict_nis.c \
dict_nisplus.c dict_open.c dict_pcre.c dict_pgsql.c dict_regexp.c \
dict_cidr.c dict_ht.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 \
@@ -33,8 +33,8 @@ 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_cidr.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \
dict_nisplus.o dict_open.o dict_pcre.o dict_pgsql.o dict_regexp.o \
dict_cidr.o dict_ht.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 \
@@ -61,8 +61,8 @@ OBJS = alldig.o argv.o argv_split.o attr_print0.o attr_print64.o \
uppercase.o
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_cidr.h dict_ht.h dict_ldap.h dict_mysql.h dict_ni.h dict_nis.h \
dict_nisplus.h dict_pcre.h dict_pgsql.h dict_regexp.h \
dict_cidr.h dict_ht.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 host_port.h htable.h inet_addr_host.h \
@@ -647,10 +647,6 @@ dict_ht.o: vstream.h
dict_ht.o: vbuf.h
dict_ht.o: argv.h
dict_ht.o: dict_ht.h
dict_ldap.o: dict_ldap.c
dict_ldap.o: sys_defs.h
dict_mysql.o: dict_mysql.c
dict_mysql.o: sys_defs.h
dict_ni.o: dict_ni.c
dict_ni.o: sys_defs.h
dict_nis.o: dict_nis.c
@@ -689,9 +685,6 @@ dict_open.o: dict_db.h
dict_open.o: dict_nis.h
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
@@ -714,8 +707,6 @@ 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

@@ -172,9 +172,6 @@
#include <dict_nis.h>
#include <dict_nisplus.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>
@@ -211,15 +208,6 @@ static DICT_OPEN_INFO dict_open_info[] = {
#ifdef HAS_NETINFO
DICT_TYPE_NETINFO, dict_ni_open,
#endif
#ifdef HAS_LDAP
DICT_TYPE_LDAP, dict_ldap_open,
#endif
#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

@@ -174,7 +174,7 @@ void msg_syslog_init(const char *name, int logopt, int facility)
* This scrubbing code is in the wrong place.
*/
if (unsafe())
putenv("TZ=");
putenv("TZ=UTC");
tzset();
openlog(name, LOG_NDELAY | logopt, facility);
if (first_call) {

View File

@@ -257,6 +257,7 @@ extern int opterr;
#define NATIVE_NEWALIAS_PATH "/usr/ucb/newaliases"
#define NATIVE_COMMAND_DIR "/usr/etc"
#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
#define STRCASECMP_IN_STRINGS_H
#endif
/*

View File

@@ -95,7 +95,7 @@ int vstring_get(VSTRING *vp, VSTREAM *fp)
break;
}
VSTRING_TERMINATE(vp);
return (VSTRING_GET_RESULT(vp));
return (c == VSTREAM_EOF ? c : VSTRING_GET_RESULT(vp));
}
/* vstring_get_nonl - read line from file, strip newline */
@@ -108,7 +108,7 @@ int vstring_get_nonl(VSTRING *vp, VSTREAM *fp)
while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
return (c == '\n' ? c : VSTRING_GET_RESULT(vp));
return (c == '\n' || c == VSTREAM_EOF ? c : VSTRING_GET_RESULT(vp));
}
/* vstring_get_null - read null-terminated string from file */
@@ -121,7 +121,7 @@ int vstring_get_null(VSTRING *vp, VSTREAM *fp)
while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
return (c == 0 ? c : VSTRING_GET_RESULT(vp));
return (c == 0 || c == VSTREAM_EOF ? c : VSTRING_GET_RESULT(vp));
}
/* vstring_get_bound - read line from file, keep newline, up to bound */
@@ -140,7 +140,7 @@ int vstring_get_bound(VSTRING *vp, VSTREAM *fp, int bound)
break;
}
VSTRING_TERMINATE(vp);
return (VSTRING_GET_RESULT(vp));
return (c == VSTREAM_EOF ? c : VSTRING_GET_RESULT(vp));
}
/* vstring_get_nonl_bound - read line from file, strip newline, up to bound */
@@ -156,7 +156,7 @@ int vstring_get_nonl_bound(VSTRING *vp, VSTREAM *fp, int bound)
while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
return (c == '\n' ? c : VSTRING_GET_RESULT(vp));
return (c == '\n' || c == VSTREAM_EOF ? c : VSTRING_GET_RESULT(vp));
}
/* vstring_get_null_bound - read null-terminated string from file */
@@ -172,7 +172,7 @@ int vstring_get_null_bound(VSTRING *vp, VSTREAM *fp, int bound)
while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
return (c == 0 ? c : VSTRING_GET_RESULT(vp));
return (c == 0 || c == VSTREAM_EOF ? c : VSTRING_GET_RESULT(vp));
}
#ifdef TEST