Web sites:
http://www.postfix.org/ current release information
- http://www.ibm.com/alphaworks/ original distribution site (obsolete)
Mail addresses (PLEASE send questions to the mailing list)
src/smtp/ SMTP client
src/smtpd/ SMTP server
src/trivial-rewrite/ Address rewriting and resolving
+ src/virtual/ virtual mailbox-only delivery agent
Test programs:
aliases yes (can enable/disable mail to /file or |command)
bare newlines yes (but will send CRLF)
blacklisting yes (client name/addr; helo hostname; mail from; rcpt to)
-content filter no
+content filter yes
db tables yes (compile time option)
dbm tables yes (compile time option)
delivered-to yes
-dsn not yet
+dsn not yet (bounces have DSN form)
errors-to: yes
esmtp yes
etrn support yes (uses per-destination log or flushes entire queue)
-fcntl locking yes (compile time)
-flock locking yes (compile time)
+fcntl locking yes (runtime configurable)
+flock locking yes (runtime configurable)
home mailbox yes
ident lookup no
ldap tables yes (contributed)
-luser relay not yet
+luser relay yes
+lmtp support yes (client)
m4 config no
mail to command yes (configurable for .forward, aliases, :include:)
mail to file yes (configurable for .forward, aliases, :include:)
majordomo yes (edit approve script to delete /delivered-to/i)
mime conversion not yet; postfix uses just-send-eight
missing <> yes (most common address forms)
+mysql tables yes (contributed)
netinfo tables yes (contributed)
newaliases yes (main alias database only)
nis tables yes
pop/imap yes (with third-party daemons that use /var[/spool]/mail)
rbl support yes
return-receipt: not yet
+sasl support yes (compile time option)
+sendmail -bt no
sendmail -q yes
-sendmail -qRxxx no
+sendmail -qRxxx yes
sendmail -qSxxx no
sendmail -qtime ignored
sendmail -v no
tcp wrapper no (use built-in blacklist facility)
user+extension yes (also: .forward+extension)
user-extension yes (also: .forward-extension)
-user.lock yes (compile time)
+user.lock yes (runtime configurable)
uucp support yes (sends user@domain recipients)
virtual domains yes
year 2000 safe yes
Feature: SASL support for the LMTP client. Recent CYRUS
software requires this for Postfix over TCP sockets.
- This was just a cloning operation.
20010120
20010121
+ Workaround: specify "broken_sasl_auth_clients = yes" in
+ order to support old Microsoft clients that implement
+ a non-standard version of RFC 2554 (AUTH command).
+
Workaround: Lotus Domino 5.0.4 violates RFC 2554 and replies
to EHLO with AUTH=LOGIN. File: smtp/smtp_proto.c.
by Laurent Wacrenier (teaser.fr). Files: util/dict*.[hc].
Code cleanup: INSTALL.sh does not ask questions when stdin
- is not connected to a tty. To automate a customized install,
- the script imports environment variables for install_root
- etc.
+ is not connected to a tty (as in: make install</dev/null).
+ To automate a customized install, the script imports
+ environment variables for install_root etc.
20010127
- Workaround: randomize the delay between lock attempts, so
- that multiple defer servers are less likely to retry at
- the same time. likely. File: util/rand_sleep.c,
- global/deliver_flock.c, global/dot_lockfile.c.
+ Workaround: randomize the delay between attempts to lock
+ a file, so that multiple bounce or defer servers are less
+ likely to retry all at the same time. likely. File:
+ util/rand_sleep.c, global/deliver_flock.c, global/dot_lockfile.c.
+
+20010128
+
+ Code cleanup: complaints about invalid or numeric hostnames
+ either provide specific context or are removed as redundant.
+ Files: util/valid_hostname.c dns/dns_lookup.c.
+
+ Code cleanup: new mailbox_size_limit parameter (default:
+ 20MB). Until now, the mailbox size limit was the same as
+ the message size limit, due to artefact of implementation.
+ Files: global/mail_params.h, local/local.c.
+
+ Bugfix: fix for the ldap_domains parameter, both semantics
+ and documentation by LaMont Jones. Files: LDAP_README,
+ conf/sample-ldap.cf, util/dict_ldap.c.
+
+ Update: merged in the virtual delivery agent by Andrew
+ McNamara. See VIRTUAL_README for detailed examples.
+
+ Update: merged a re-vamped nqmgr by Patrik Rak.
substitute for the address Postfix is trying to resolve, e.g.
ldapsource_query_filter = (&(mail=%s)(paid_up=true))
- domain (No default; you must configure this.)
+ domain (Default is to ignore this.)
This is a list of domain names, paths to files, or dictionaries.
- If specified, only lookups ending in a domain on this list will
- be searched. This can significantly reduce the query load on the
- LDAP server.
+ If specified, only lookups for the domains on this list will be
+ performed. This means that the LDAP map won't get searched for
+ 'user', nor will it get searched for any domain not listed. This
+ can significantly reduce the query load on the LDAP server.
ldapsource_domain = postfix.org, hash:/etc/postfix/searchdomains
result_attribute (maildrop)
+[This file still needs to be updated - some information is obsolete]
+
Postfix LMTP support
====================
src/lmtp src/trivial-rewrite src/qmgr 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/postsuper \
- src/nqmgr src/spawn src/flush # proto man html
+ src/nqmgr src/spawn src/flush src/virtual # proto man html
default: update
-REJECT by header/body_checks are now flagged as policy violations
-rather than bounces, for consistency in postmaster notifications.
+Apart from bugfixes this is expected to become the first non-beta
+Postfix release.
+
+Incompatible changes with snapshot-20010128
+===========================================
+
+REJECT in header/body_checks is now flagged as policy violation
+rather than bounce, for consistency in postmaster notifications.
+
+The mailbox size limit for local delivery is no longer controlled
+by the message_size_limit paramater, but by a separate parameter
+called mailbox_size_limit (default: 20MBytes).
+
+The default RBL (real-time blackhole lists) domain examples have
+been updated from *.vix.com to *.mail-abuse.org.
+
+Major changes with snapshot-20010128
+====================================
+
+Updated nqmgr (experimental queue manager with clever queueing
+strategy) by Patrik Rak. This code is still new. Once it stops
+changing (for a long time!) it will become part of the non-beta
+release.
+
+Virtual mailbox delivery agent by Andrew McNamara. This delivery
+agent can deliver mail for any number of domains. See the file
+VIRTUAL_README for detailed examples. This code is still new. Once
+it stops changing it will become part of the non-beta release.
+
+Many "valid_hostname" warnings were either eliminated, and the rest
+was replaced by something more informative.
+
+SASL support (RFC 2554) for the LMTP delivery agent. This is required
+by recent Cyrus implementations when delivering mail over TCP
+sockets. The LMTP_README file has been updated but still contains
+some obsolete information.
+
+Workarounds for non-standard RFC 2554 (AUTH command) implementations.
+Specify "broken_sasl_auth_clients = yes" to enable SMTP server
+support for old Microsoft client applications. The Postfix SMTP
+client supports non-standard RFC 2554 servers by default.
Major changes with snapshot-20001217
====================================
--- /dev/null
+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 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 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 location, uid and gid of user mailboxes 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 not support aliases or .forwards (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. All deliveries are done with the privileges of
+the recipient.
+
+This delivery agent requires three different lookup tables in order
+to define its recipients. This is because Postfix table lookups
+can't return multiple results. Until that limitation is fixed, use
+an LDAP or MYSQL database if it is too inconvenient for you to
+maintain three parallel tables (or generate the three 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_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.
+
+ If a recipient is not found the mail is returned to the sender.
+
+ 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.
+
+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.
+
+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 -m" command to find out what locking methods
+ Postfix supports on your system.
+
+virtual_mailbox_size
+
+ An upper limit on the size of a mailbox 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, and no lookups of recipients in /etc/passwd.
+
+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:
+ local_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
+
+ # All domains that have final delivery on this machine
+
+ mydestination = $myhostname virtual1.domain virtual2.domain
+
+ # Reject unknown recipients at the SMTP port
+
+ local_recipient_maps = $virtual_mailbox_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/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.
+
+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:
+ 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
+ transport_maps = hash:/etc/postfix/transport
+
+ # All domains that have final delivery on this machine
+
+ mydestination =
+ $myhostname $localhost.$mydomain virtual1.domain virtual2.domain
+
+ # Reject unknown local and virtual recipients at the SMTP port
+
+ local_recipient_maps =
+ unix:passwd.byname $alias_maps $virtual_mailbox_maps
+
+ # Define a virtual delivery agent if the entry doesn't already exist
+
+ /etc/postfix/master.cf:
+ virtual unix - n n - - virtual
+
+ # Route specific domains to the virtual delivery agent; by default,
+ # mail for domains in $mydestination goes to the local delivery agent
+
+ /etc/postfix/transport:
+ virtual1.domain virtual
+ virtual2.domain 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
+
+Remember that each domain is required to have a postmaster contact
+address.
+
+Example 3: 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 4: 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.name user@domain.name, user@autoreply.domain.name
+
+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.name into some script that sends an automatic
+reply back to the sender.
showq unix n - n - - showq
error unix - - n - - error
local unix - n n - - local
+virtual unix - n n - - virtual
lmtp unix - - n - - lmtp
cyrus unix - n n - - pipe
flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
# ldap_open(3) man page.
#
#ldap_dereference = 0
+
+# The ldap_domain parameter limits the LDAP searches to just things in
+# (exactly) the specified list of domains.
+#
+#ldap_domain =
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
+ nqmgr.8.html spawn.8.html flush.8.html virtual.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 \
postlog.1.html postdrop.1.html postmap.1.html sendmail.1.html \
smtpd.8.html: ../src/smtpd/smtpd.c
srctoman $? | nroff -man | man2html | postlink >$@
+virtual.8.html: ../src/virtual/virtual.c
+ srctoman $? | nroff -man | man2html | postlink >$@
+
trivial-rewrite.8.html: ../src/trivial-rewrite/trivial-rewrite.c
srctoman $? | nroff -man | man2html | postlink >$@
<p>
+<li>The <a href="virtual.8.html">virtual</a> delivery agent is a
+very much stripped down version of the local delivery agent that
+delivers to mailboxes only. This is the most secure Postfix delivery
+agent, because it does not aliases expansions and no .forward file
+expansions.
+
+<p>
+
+This delivery agent can deliver mail for multiple domains, which
+makes it especially suitable for hosting lots of small domains on
+a single machine.
+
+<p>
+
<li>The <a href="smtp.8.html">SMTP client</a> looks up a list of
mail exchangers for the destination host, sorts the list by
preference, and tries each address in turn until it finds a server
<p>
+<li>The <a href="lmtp.8.html">LMTP client</a> speaks a protocol
+similar to SMTP. The client can connect to local or remote mailbox
+servers such as Cyrus. All the queue management is done by Postfix.
+The advantage of this setup is that one Postfix machine can feed
+multiple mailbox servers over LMTP. The opposite is true as well:
+one mailbox server can be fed over LMTP by multiple Postfix machines.
+
+<p>
+
<li>The <a href="pipe.8.html">pipe mailer</a> is the outbound
interface to other mail transports (the <a
href="sendmail.1.html">sendmail</a> program is the inbound interface).
--- /dev/null
+<html> <head> </head> <body> <pre>
+
+
+
+VIRTUAL(8) VIRTUAL(8)
+
+
+<b>NAME</b>
+ virtual - Postfix virtual domain mail delivery agent
+
+<b>SYNOPSIS</b>
+ <b>virtual</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <b>virtual</b> delivery agent is designed for virtual mail
+ hosting services. Originally based on the Postfix local
+ delivery agent, this agent looks up recipients with map
+ lookups of their full recipient address, instead of using
+ hard-coded unix password file lookups of the address local
+ part only.
+
+ This delivery agent only delivers mail. Other features
+ such as mail forwarding, out-of-office notifications,
+ etc., must be configured via virtual maps or via similar
+ lookup mechanisms.
+
+<b>MAILBOX</b> <b>LOCATION</b>
+ The mailbox location is controlled by the <b>virtual</b><i>_</i><b>mail-</b>
+ <b>box</b><i>_</i><b>base</b> and <b>virtual</b><i>_</i><b>mailbox</b><i>_</i><b>maps</b> configuration parameters
+ (see below). The <b>virtual</b><i>_</i><b>mailbox</b><i>_</i><b>maps</b> table is indexed by
+ the full recipient address.
+
+ The mailbox pathname is constructed as follows:
+
+ <b>$virtual</b><i>_</i><b>mailbox</b><i>_</i><b>base/$virtual</b><i>_</i><b>mailbox</b><i>_</i><b>maps(</b><i>recipient</i><b>)</b>
+
+ where <i>recipient</i> is the full recipient address.
+
+<b>UNIX</b> <b>MAILBOX</b> <b>FORMAT</b>
+ When the mailbox location does not end in <b>/</b>, the message
+ is delivered in UNIX mailbox format. This format stores
+ multiple messages in one textfile.
+
+ The <b>virtual</b> delivery agent prepends a "<b>From</b> <i>sender</i>
+ <i>time_stamp</i>" envelope header to each message, prepends a
+ <b>Delivered-To:</b> message header with the envelope recipient
+ address, prepends a <b>Return-Path:</b> message header with the
+ envelope sender address, prepends a > character to lines
+ beginning with "<b>From</b> ", and appends an empty line.
+
+ The mailbox is locked for exclusive access while delivery
+ is in progress. In case of problems, an attempt is made to
+ truncate the mailbox to its original length.
+
+<b>QMAIL</b> <b>MAILDIR</b> <b>FORMAT</b>
+ When the mailbox location ends in <b>/</b>, the message is deliv-
+ ered in qmail <b>maildir</b> format. This format stores one mes-
+ sage per file.
+
+ The <b>virtual</b> delivery agent daemon prepends a <b>Delivered-To:</b>
+ message header with the envelope recipient address and
+
+
+
+ 1
+
+
+
+
+
+VIRTUAL(8) VIRTUAL(8)
+
+
+ prepends a <b>Return-Path:</b> message header with the envelope
+ sender address.
+
+ By definition, <b>maildir</b> format does not require file lock-
+ ing during mail delivery or retrieval.
+
+<b>MAILBOX</b> <b>OWNERSHIP</b>
+ Mailbox ownership is controlled by the <b>virtual</b><i>_</i><b>uid</b><i>_</i><b>maps</b>
+ and <b>virtual</b><i>_</i><b>gid</b><i>_</i><b>maps</b> lookup tables, which are indexed with
+ the full recipient address. Each table provides a string
+ with the numerical user and group ID, respectively.
+
+ The <b>virtual</b><i>_</i><b>minimum</b><i>_</i><b>uid</b> parameter imposes a lower bound on
+ numerical user ID values that may be specified in any <b>vir-</b>
+ <b>tual</b><i>_</i><b>owner</b><i>_</i><b>maps</b> or <b>virtual</b><i>_</i><b>uid</b><i>_</i><b>maps</b>.
+
+<b>SECURITY</b>
+ The virtual delivery agent is not security sensitive, provided
+ that the lookup tables with recipient information are adequately
+ protected. This program is not designed to run chrooted.
+
+<b>STANDARDS</b>
+ <a href="http://www.faqs.org/rfcs/rfc822.html">RFC 822</a> (ARPA Internet Text Messages)
+
+<b>DIAGNOSTICS</b>
+ Mail bounces when the recipient has no mailbox or when the
+ recipient is over disk quota. In all other cases, mail for
+ an existing recipient is deferred and a warning is logged.
+
+ Problems and transactions are logged to <b>syslogd</b>(8). Cor-
+ rupted message files are marked so that the queue manager
+ can move them to the <b>corrupt</b> queue afterwards.
+
+ Depending on the setting of the <b>notify</b><i>_</i><b>classes</b> parameter,
+ the postmaster is notified of bounces and of other trou-
+ ble.
+
+<b>BUGS</b>
+ This delivery agent silently ignores address extensions.
+
+ Postfix should have lookup tables that can return multiple
+ result attributes. In order to avoid the inconvenience of
+ maintaining three tables, use an LDAP or MYSQL database.
+
+<b>CONFIGURATION</b> <b>PARAMETERS</b>
+ The following <b>main.cf</b> parameters are especially relevant
+ to this program. 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 configuration change.
+
+<b>Mailbox</b> <b>delivery</b>
+ <b>virtual</b><i>_</i><b>mailbox</b><i>_</i><b>base</b>
+ Specifies a path that is prepended to all mailbox
+ or maildir paths. This is a safety measure to
+
+
+
+ 2
+
+
+
+
+
+VIRTUAL(8) VIRTUAL(8)
+
+
+ ensure that an out of control map in <b>virtual</b><i>_</i><b>mail-</b>
+ <b>box</b><i>_</i><b>maps</b> doesn't litter the filesystem with mail-
+ boxes. While it could be set to "/", this setting
+ isn't recommended.
+
+ <b>virtual</b><i>_</i><b>mailbox</b><i>_</i><b>maps</b>
+ Recipients are looked up in these maps to determine
+ the path to their mailbox or maildir. If the
+ returned path ends in a slash ("/"), maildir-style
+ delivery is carried out, otherwise the path is
+ assumed to specify a UNIX-style mailbox file.
+
+ Note that <b>virtual</b><i>_</i><b>mailbox</b><i>_</i><b>base</b> is unconditionally
+ prepended to this path.
+
+ <b>virtual</b><i>_</i><b>minimum</b><i>_</i><b>uid</b>
+ Specifies a minimum uid that will be accepted as a
+ return from a <b>virtual</b><i>_</i><b>owner</b><i>_</i><b>maps</b> or <b>vir-</b>
+ <b>tual</b><i>_</i><b>uid</b><i>_</i><b>maps</b> lookup. Returned values less than
+ this will be rejected, and the message will be
+ deferred.
+
+ <b>virtual</b><i>_</i><b>uid</b><i>_</i><b>maps</b>
+ Recipients are looked up in these maps to determine
+ the user ID to be used when writing to the target
+ mailbox.
+
+ <b>virtual</b><i>_</i><b>gid</b><i>_</i><b>maps</b>
+ Recipients are looked up in these maps to determine
+ the group ID to be used when writing to the target
+ mailbox.
+
+<b>Locking</b> <b>controls</b>
+ <b>virtual</b><i>_</i><b>mailbox</b><i>_</i><b>lock</b>
+ How to lock UNIX-style mailboxes: one or more of
+ <b>flock</b>, <b>fcntl</b> or <b>dotlock</b>. The <b>dotlock</b> method
+ requires that the recipient UID or GID has write
+ access to the parent directory of the mailbox file.
+
+ This setting is ignored with <b>maildir</b> style deliv-
+ ery, because such deliveries are safe without
+ explicit locks.
+
+ Use the command <b>postconf</b> <b>-m</b> to find out what lock-
+ ing methods are available on your system.
+
+ <b>deliver</b><i>_</i><b>lock</b><i>_</i><b>attempts</b>
+ Limit the number of attempts to acquire an exclu-
+ sive lock on a UNIX-style mailbox file.
+
+ <b>deliver</b><i>_</i><b>lock</b><i>_</i><b>delay</b>
+ Time (default: seconds) between successive attempts
+ to acquire an exclusive lock on a UNIX-style mail-
+ box file. The actual delay is slightly randomized.
+
+
+
+ 3
+
+
+
+
+
+VIRTUAL(8) VIRTUAL(8)
+
+
+ <b>stale</b><i>_</i><b>lock</b><i>_</i><b>time</b>
+ Limit the time after which a stale lockfile is
+ removed (applicable to UNIX-style mailboxes only).
+
+<b>Resource</b> <b>controls</b>
+ <b>virtual</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b>
+ Limit the number of parallel deliveries to the same
+ domain via the <b>virtual</b> delivery agent. The default
+ limit is taken from the <b>default</b><i>_</i><b>destination</b><i>_</i><b>concur-</b>
+ <b>rency</b><i>_</i><b>limit</b> parameter. The limit is enforced by
+ the Postfix queue manager.
+
+ <b>virtual</b><i>_</i><b>destination</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
+ Limit the number of recipients per message delivery
+ via the <b>virtual</b> delivery agent. The default limit
+ is taken from the <b>default</b><i>_</i><b>destination</b><i>_</i><b>recipi-</b>
+ <b>ent</b><i>_</i><b>limit</b> parameter. The limit is enforced by the
+ Postfix queue manager.
+
+ <b>virtual</b><i>_</i><b>mailbox</b><i>_</i><b>limit</b>
+ The maximal size in bytes of a mailbox or maildir
+ file.
+
+<b>HISTORY</b>
+ This agent was originally based on the Postfix local
+ delivery agent. Modifications mainly consisted of removing
+ code that either was not applicable or that was not safe
+ in this context: aliases, ~user/.forward files, delivery
+ to "|command" or to /file/name.
+
+ The <b>Delivered-To:</b> header appears in the <b>qmail</b> system by
+ Daniel Bernstein.
+
+ The <b>maildir</b> structure appears in the <b>qmail</b> system by
+ Daniel Bernstein.
+
+<b>SEE</b> <b>ALSO</b>
+ <a href="bounce.8.html">bounce(8)</a> non-delivery status reports
+ syslogd(8) system logging
+ <a href="qmgr.8.html">qmgr(8)</a> queue manager
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this
+ software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Andrew McNamara
+ andrewm@connect.com.au
+ connect.com.au Pty. Ltd.
+
+
+
+ 4
+
+
+
+
+
+VIRTUAL(8) VIRTUAL(8)
+
+
+ Level 3, 213 Miller St
+ North Sydney 2060, NSW, Australia
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+</pre> </body> </html>
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/nqmgr.8 man8/spawn.8 man8/flush.8 man8/virtual.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 \
man1/postmap.1 man1/sendmail.1 man1/mailq.1 man1/newaliases.1 \
man8/smtpd.8: ../src/smtpd/smtpd.c
../mantools/srctoman $? >$@
+man8/virtual.8: ../src/virtual/virtual.c
+ ../mantools/srctoman $? >$@
+
man8/trivial-rewrite.8: ../src/trivial-rewrite/trivial-rewrite.c
../mantools/srctoman $? >$@
--- /dev/null
+.TH VIRTUAL 8
+.ad
+.fi
+.SH NAME
+virtual
+\-
+Postfix virtual domain mail delivery agent
+.SH SYNOPSIS
+.na
+.nf
+\fBvirtual\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+The \fBvirtual\fR delivery agent is designed for virtual mail
+hosting services. Originally based on the Postfix local delivery
+agent, this agent looks up recipients with map lookups of their
+full recipient address, instead of using hard-coded unix password
+file lookups of the address local part only.
+
+This delivery agent only delivers mail. Other features such as
+mail forwarding, out-of-office notifications, etc., must be
+configured via virtual maps or via similar lookup mechanisms.
+.SH MAILBOX LOCATION
+.na
+.nf
+.ad
+.fi
+The mailbox location is controlled by the \fBvirtual_mailbox_base\fR
+and \fBvirtual_mailbox_maps\fR configuration parameters (see below).
+The \fBvirtual_mailbox_maps\fR table is indexed by the full recipient
+address.
+
+The mailbox pathname is constructed as follows:
+
+.ti +2
+\fB$virtual_mailbox_base/$virtual_mailbox_maps(\fIrecipient\fB)\fR
+
+where \fIrecipient\fR is the full recipient address.
+.SH UNIX MAILBOX FORMAT
+.na
+.nf
+.ad
+.fi
+When the mailbox location does not end in \fB/\fR, the message
+is delivered in UNIX mailbox format. This format stores multiple
+messages in one textfile.
+
+The \fBvirtual\fR delivery agent prepends a "\fBFrom \fIsender
+time_stamp\fR" envelope header to each message, prepends a
+\fBDelivered-To:\fR message header with the envelope recipient
+address, prepends a \fBReturn-Path:\fR message header with the
+envelope sender address, prepends a \fB>\fR character to lines
+beginning with "\fBFrom \fR", and appends an empty line.
+
+The mailbox is locked for exclusive access while delivery is in
+progress. In case of problems, an attempt is made to truncate the
+mailbox to its original length.
+.SH QMAIL MAILDIR FORMAT
+.na
+.nf
+.ad
+.fi
+When the mailbox location ends in \fB/\fR, the message is delivered
+in qmail \fBmaildir\fR format. This format stores one message per file.
+
+The \fBvirtual\fR delivery agent daemon prepends a \fBDelivered-To:\fR
+message header with the envelope recipient address and prepends a
+\fBReturn-Path:\fR message header with the envelope sender address.
+
+By definition, \fBmaildir\fR format does not require file locking
+during mail delivery or retrieval.
+.SH MAILBOX OWNERSHIP
+.na
+.nf
+.ad
+.fi
+Mailbox ownership is controlled by the \fBvirtual_uid_maps\fR
+and \fBvirtual_gid_maps\fR lookup tables, which are indexed
+with the full recipient address. Each table provides
+a string with the numerical user and group ID, respectively.
+
+The \fBvirtual_minimum_uid\fR parameter imposes a lower bound on
+numerical user ID values that may be specified in any
+\fBvirtual_owner_maps\fR or \fBvirtual_uid_maps\fR.
+.SH SECURITY
+.na
+.nf
+The virtual delivery agent is not security sensitive, provided
+that the lookup tables with recipient information are adequately
+protected. This program is not designed to run chrooted.
+.SH STANDARDS
+.na
+.nf
+RFC 822 (ARPA Internet Text Messages)
+.SH DIAGNOSTICS
+.ad
+.fi
+Mail bounces when the recipient has no mailbox or when the
+recipient is over disk quota. In all other cases, mail for
+an existing recipient is deferred and a warning is logged.
+
+Problems and transactions are logged to \fBsyslogd\fR(8).
+Corrupted message files are marked so that the queue
+manager can move them to the \fBcorrupt\fR queue afterwards.
+
+Depending on the setting of the \fBnotify_classes\fR parameter,
+the postmaster is notified of bounces and of other trouble.
+.SH BUGS
+.ad
+.fi
+This delivery agent silently ignores address extensions.
+
+Postfix should have lookup tables that can return multiple result
+attributes. In order to avoid the inconvenience of maintaining
+three tables, use an LDAP or MYSQL database.
+.SH CONFIGURATION PARAMETERS
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant to
+this program. See the Postfix \fBmain.cf\fR file for syntax details
+and for default values. Use the \fBpostfix reload\fR command after
+a configuration change.
+.SH Mailbox delivery
+.ad
+.fi
+.IP \fBvirtual_mailbox_base\fR
+Specifies a path that is prepended to all mailbox or maildir paths.
+This is a safety measure to ensure that an out of control map in
+\fBvirtual_mailbox_maps\fR doesn't litter the filesystem with mailboxes.
+While it could be set to "/", this setting isn't recommended.
+.IP \fBvirtual_mailbox_maps\fR
+Recipients are looked up in these maps to determine the path to
+their mailbox or maildir. If the returned path ends in a slash
+("/"), maildir-style delivery is carried out, otherwise the
+path is assumed to specify a UNIX-style mailbox file.
+
+Note that \fBvirtual_mailbox_base\fR is unconditionally prepended
+to this path.
+.IP \fBvirtual_minimum_uid\fR
+Specifies a minimum uid that will be accepted as a return from
+a \fBvirtual_owner_maps\fR or \fBvirtual_uid_maps\fR lookup.
+Returned values less than this will be rejected, and the message
+will be deferred.
+.IP \fBvirtual_uid_maps\fR
+Recipients are looked up in these maps to determine the user ID to be
+used when writing to the target mailbox.
+.IP \fBvirtual_gid_maps\fR
+Recipients are looked up in these maps to determine the group ID to be
+used when writing to the target mailbox.
+.SH "Locking controls"
+.ad
+.fi
+.IP \fBvirtual_mailbox_lock\fR
+How to lock UNIX-style mailboxes: one or more of \fBflock\fR,
+\fBfcntl\fR or \fBdotlock\fR. The \fBdotlock\fR method requires
+that the recipient UID or GID has write access to the parent
+directory of the mailbox file.
+
+This setting is ignored with \fBmaildir\fR style delivery,
+because such deliveries are safe without explicit locks.
+
+Use the command \fBpostconf -m\fR to find out what locking methods
+are available on your system.
+.IP \fBdeliver_lock_attempts\fR
+Limit the number of attempts to acquire an exclusive lock
+on a UNIX-style mailbox file.
+.IP \fBdeliver_lock_delay\fR
+Time (default: seconds) between successive attempts to acquire
+an exclusive lock on a UNIX-style mailbox file. The actual delay
+is slightly randomized.
+.IP \fBstale_lock_time\fR
+Limit the time after which a stale lockfile is removed (applicable
+to UNIX-style mailboxes only).
+.SH "Resource controls"
+.ad
+.fi
+.IP \fBvirtual_destination_concurrency_limit\fR
+Limit the number of parallel deliveries to the same domain
+via the \fBvirtual\fR delivery agent.
+The default limit is taken from the
+\fBdefault_destination_concurrency_limit\fR parameter.
+The limit is enforced by the Postfix queue manager.
+.IP \fBvirtual_destination_recipient_limit\fR
+Limit the number of recipients per message delivery
+via the \fBvirtual\fR delivery agent.
+The default limit is taken from the
+\fBdefault_destination_recipient_limit\fR parameter.
+The limit is enforced by the Postfix queue manager.
+.IP \fBvirtual_mailbox_limit\fR
+The maximal size in bytes of a mailbox or maildir file.
+.SH HISTORY
+.na
+.nf
+.ad
+.fi
+This agent was originally based on the Postfix local delivery
+agent. Modifications mainly consisted of removing code that either
+was not applicable or that was not safe in this context: aliases,
+~user/.forward files, delivery to "|command" or to /file/name.
+
+The \fBDelivered-To:\fR header appears in the \fBqmail\fR system
+by Daniel Bernstein.
+
+The \fBmaildir\fR structure appears in the \fBqmail\fR system
+by Daniel Bernstein.
+.SH SEE ALSO
+.na
+.nf
+bounce(8) non-delivery status reports
+syslogd(8) system logging
+qmgr(8) queue manager
+.SH LICENSE
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH AUTHOR(S)
+.na
+.nf
+Wietse Venema
+IBM T.J. Watson Research
+P.O. Box 704
+Yorktown Heights, NY 10598, USA
+
+Andrew McNamara
+andrewm@connect.com.au
+connect.com.au Pty. Ltd.
+Level 3, 213 Miller St
+North Sydney 2060, NSW, Australia
return (DNS_OK);
}
+/* valid_rr_name - validate hostname in resource record */
+
+static int valid_rr_name(const char *name, const char *location,
+ unsigned type, DNS_REPLY *reply)
+{
+ char temp[DNS_NAME_LEN];
+ char *query_name;
+ int len;
+ char *gripe;
+ int result;
+
+ /*
+ * People aren't supposed to specify numeric names where domain names are
+ * required, but it "works" with some mailers anyway, so people complain
+ * when software doesn't bend over backwards.
+ */
+#define PASS_NAME 1
+#define REJECT_NAME 0
+
+ if (valid_hostaddr(name, DONT_GRIPE)) {
+ result = PASS_NAME;
+ gripe = "numeric domain name";
+ } else if (!valid_hostname(name, DO_GRIPE)) {
+ result = REJECT_NAME;
+ gripe = "malformed domain name";
+ } else {
+ result = PASS_NAME;
+ gripe = 0;
+ }
+
+ /*
+ * If we have a gripe, show some context, including the name used in the
+ * query and the type of reply that we're looking at.
+ */
+ if (gripe) {
+ len = dn_expand(reply->buf, reply->end, reply->query_start,
+ temp, DNS_NAME_LEN);
+ query_name = (len < 0 ? "*unparsable*" : temp);
+ msg_warn("%s in %s of %s record for %s: %.100s",
+ gripe, location, dns_strtype(type), query_name, name);
+ }
+ return (result);
+}
+
/* dns_get_rr - extract resource record from name server reply */
static DNS_RR *dns_get_rr(DNS_REPLY *reply, unsigned char *pos,
case T_PTR:
if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
return (0);
- if (!valid_hostname(temp))
+ if (!valid_rr_name(temp, "resource data", fixed->type, reply))
return (0);
data_len = strlen(temp) + 1;
break;
GETSHORT(pref, pos);
if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
return (0);
- if (!valid_hostname(temp))
+ if (!valid_rr_name(temp, "resource data", fixed->type, reply))
return (0);
data_len = strlen(temp) + 1;
break;
msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
return (DNS_RETRY);
- if (!valid_hostname(cname))
+ if (!valid_rr_name(cname, "resource data", fixed->type, reply))
return (DNS_RETRY);
return (DNS_OK);
}
len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
if (len < 0)
CORRUPT;
- if (!valid_hostname(rr_name))
- CORRUPT;
- if (fqdn)
- vstring_strcpy(fqdn, rr_name);
pos += len;
/*
CORRUPT;
if (dns_get_fixed(pos, &fixed) != DNS_OK)
CORRUPT;
+ if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
+ CORRUPT;
+ if (fqdn)
+ vstring_strcpy(fqdn, rr_name);
if (msg_verbose)
msg_info("dns_get_answer: type %s for %s",
dns_strtype(fixed.type), rr_name);
/*
* The Linux resolver misbehaves when given an invalid domain name.
*/
- if (!valid_hostname(name)) {
+ if (!valid_hostname(name, DONT_GRIPE)) {
if (why)
vstring_sprintf(why, "Name service error for %s: invalid name",
name);
site = vstring_alloc(10);
queue_id = vstring_alloc(10);
if (mail_command_read(client_stream, "%s %s", site, queue_id) == 2
- && valid_hostname(STR(site))
+ && valid_hostname(STR(site), DONT_GRIPE)
&& mail_queue_id_ok(STR(queue_id)))
status = flush_add_service(lowercase(STR(site)), STR(queue_id));
mail_print(client_stream, "%d", status);
} else if (STREQ(STR(request), FLUSH_REQ_SEND)) {
site = vstring_alloc(10);
if (mail_command_read(client_stream, "%s", site) == 1
- && valid_hostname(STR(site)))
+ && valid_hostname(STR(site), DONT_GRIPE))
status = flush_send_service(lowercase(STR(site)));
mail_print(client_stream, "%d", status);
} else if (STREQ(STR(request), FLUSH_REQ_REFRESH)
deliver_flock.o: ../../include/vstring.h
deliver_flock.o: ../../include/vbuf.h
deliver_flock.o: ../../include/myflock.h
+deliver_flock.o: ../../include/iostuff.h
deliver_flock.o: mail_params.h
deliver_flock.o: deliver_flock.h
deliver_pass.o: deliver_pass.c
dot_lockfile.o: ../../include/vbuf.h
dot_lockfile.o: ../../include/stringops.h
dot_lockfile.o: ../../include/mymalloc.h
+dot_lockfile.o: ../../include/iostuff.h
dot_lockfile.o: mail_params.h
dot_lockfile.o: dot_lockfile.h
dot_lockfile_as.o: dot_lockfile_as.c
mkmap_open.o: ../../include/vstream.h
mkmap_open.o: ../../include/vbuf.h
mkmap_open.o: ../../include/argv.h
+mkmap_open.o: ../../include/dict_db.h
+mkmap_open.o: ../../include/dict_dbm.h
mkmap_open.o: ../../include/sigdelay.h
mkmap_open.o: ../../include/mymalloc.h
mkmap_open.o: ../../include/myflock.h
* the domain.
*/
get_mail_conf_str_fn_table(function_str_defaults);
- if (!valid_hostname(var_myhostname) || !valid_hostname(var_mydomain))
- msg_fatal("host or domain name configuration error");
+ if (!valid_hostname(var_myhostname, DO_GRIPE)
+ || !valid_hostname(var_mydomain, DO_GRIPE))
+ msg_fatal("main.cf configuration error: bad %s or %s parameter value",
+ VAR_MYHOSTNAME, VAR_MYDOMAIN);
/*
* Variables that are needed by almost every program.
#define VAR_MAILBOX_LOCK "mailbox_delivery_lock"
extern char *var_mailbox_lock;
+ /*
+ * Mailbox size limit. This used to be enforced as a side effect of the way
+ * the message size limit is implemented, but that is not clean.
+ */
+#define VAR_MAILBOX_LIMIT "mailbox_size_limit"
+#define DEF_MAILBOX_LIMIT (DEF_MESSAGE_LIMIT * 2)
+extern int var_mailbox_limit;
+
/*
* Miscellaneous.
*/
#define DEF_EXPORT_ENVIRON "TZ"
extern char *var_export_environ;
+ /*
+ * Tunables for the "virtual" local delivery agent
+ */
+#define VAR_VIRT_MAILBOX_MAPS "virtual_mailbox_maps"
+#define DEF_VIRT_MAILBOX_MAPS ""
+extern char *var_virt_mailbox_maps;
+
+#define VAR_VIRT_UID_MAPS "virtual_uid_maps"
+#define DEF_VIRT_UID_MAPS ""
+extern char *var_virt_uid_maps;
+
+#define VAR_VIRT_GID_MAPS "virtual_gid_maps"
+#define DEF_VIRT_GID_MAPS ""
+extern char *var_virt_gid_maps;
+
+#define VAR_VIRT_USEDOTLOCK "virtual_usedotlock"
+#define DEF_VIRT_USEDOTLOCK 0
+extern bool var_virt_usedotlock;
+
+#define VAR_VIRT_MINUID "virtual_minimum_uid"
+#define DEF_VIRT_MINUID 100
+extern int var_virt_minimum_uid;
+
+#define VAR_VIRT_MAILBOX_BASE "virtual_mailbox_base"
+#define DEF_VIRT_MAILBOX_BASE ""
+extern char *var_virt_mailbox_base;
+
+#define VAR_VIRT_MAILBOX_LIMIT "virtual_mailbox_limit"
+#define DEF_VIRT_MAILBOX_LIMIT (2 * DEF_MESSAGE_LIMIT)
+extern int var_virt_mailbox_limit;
+
+#define VAR_VIRT_MAILBOX_LOCK "virtual_mailbox_lock"
+#define DEF_VIRT_MAILBOX_LOCK "fcntl"
+extern char *var_virt_mailbox_lock;
+
/* LICENSE
/* .ad
/* .fi
/*
* OK if in valid hostname form.
*/
- return (valid_hostname(queue_id));
+ return (valid_hostname(queue_id, DO_GRIPE));
}
/* mail_queue_enter - make mail queue entry with locally-unique name */
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20010127"
+#define DEF_MAIL_VERSION "Snapshot-20010128"
extern char *var_mail_version;
/* LICENSE
peer.type = PEER_TYPE_INET;
hp = gethostbyaddr((char *) &(sin.sin_addr),
sizeof(sin.sin_addr), AF_INET);
- peer.name = (hp && valid_hostname(hp->h_name) ?
+ peer.name = (hp && valid_hostname(hp->h_name, DO_GRIPE) ?
hp->h_name : "unknown");
peer.addr = inet_ntoa(sin.sin_addr);
return (&peer);
int var_stat_home_dir;
int var_mailtool_compat;
char *var_mailbox_lock;
+int var_mailbox_limit;
int local_cmd_deliver_mask;
int local_file_deliver_mask;
local_mask_init();
}
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Reset the file size limit from the message size limit to the mailbox
+ * size limit. XXX This still isn't accurate because the file size limit
+ * also affects delivery to command.
+ *
+ * We can't have mailbox size limit smaller than the message size limit,
+ * because that prohibits the delivery agent from updating the queue
+ * file.
+ */
+ if (var_mailbox_limit < var_message_limit)
+ msg_fatal("main.cf configuration error: %s is smaller than %s",
+ VAR_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT);
+ set_file_limit(var_mailbox_limit);
+}
+
/* main - pass control to the single-threaded skeleton */
int main(int argc, char **argv)
};
static CONFIG_INT_TABLE int_table[] = {
VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
+ VAR_MAILBOX_LIMIT, DEF_MAILBOX_LIMIT, &var_mailbox_limit, 1, 0,
0,
};
static CONFIG_STR_TABLE str_table[] = {
MAIL_SERVER_RAW_TABLE, raw_table,
MAIL_SERVER_BOOL_TABLE, bool_table,
MAIL_SERVER_TIME_TABLE, time_table,
+ MAIL_SERVER_PRE_INIT, pre_init,
MAIL_SERVER_POST_INIT, post_init,
MAIL_SERVER_PRE_ACCEPT, pre_accept,
0);
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) { \
object->peers.next = head.next; \
object->peers.prev = 0; \
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 on
- * the job stack */
+ 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) */
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) */
- QMGR_JOB_LIST job_stack; /* job stack for tracking preemption */
+ * 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 */
};
QMGR_ENTRY_LIST busy; /* messages on the wire */
QMGR_QUEUE_LIST peers; /* neighbor queues */
char *reason; /* why unavailable */
+ int blocker_tag; /* tagged if blocks job list */
};
#define QMGR_QUEUE_TODO 1 /* waiting for service */
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 stack_peers; /* transport stack 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
- * retired) */
+ * 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 */
/* NAME
/* qmgr_deliver 3
/* SUMMARY
-/* deliver one pe-site queue entry to that site
+/* deliver one per-site queue entry to that site
/* SYNOPSIS
/* #include "qmgr.h"
/*
QMGR_PEER *peer = entry->peer;
QMGR_JOB *sponsor,
*job = peer->job;
+ QMGR_TRANSPORT *transport = job->transport;
/*
* Take this entry off the in-core queue.
/*
* 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 in
+ * 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) {
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.
+ *
+ * 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;
+ }
+ 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.
#define MIN_ENTRIES(job) ((job)->read_entries)
#define MAX_ENTRIES(job) ((job)->read_entries + (job)->message->rcpt_unread)
-#define RESET_CANDIDATE_CACHE(transport) do { \
- (transport)->candidate_cache_time = (time_t) 0; \
- (transport)->candidate_cache = 0; \
- } while(0)
+#define RESET_CANDIDATE_CACHE(transport) ((transport)->candidate_cache_current = 0)
+
+#define IS_BLOCKER(job,transport) ((job)->blocker_tag == (transport)->blocker_tag)
/* qmgr_job_create - create and initialize message job structure */
htable_enter(transport->job_byname, message->queue_id, (char *) job);
job->transport = transport;
QMGR_LIST_INIT(job->transport_peers);
+ QMGR_LIST_INIT(job->time_peers);
+ job->stack_parent = 0;
+ QMGR_LIST_INIT(job->stack_children);
+ QMGR_LIST_INIT(job->stack_siblings);
+ job->stack_level = -1;
+ job->blocker_tag = 0;
job->peer_byname = htable_create(0);
QMGR_LIST_INIT(job->peer_list);
- job->stack_level = 0;
job->slots_used = 0;
job->slots_available = 0;
job->selected_entries = 0;
return (job);
}
-/* qmgr_job_link - append the job to the job list, according to the time it was queued */
+/* qmgr_job_link - append the job to the job lists based on the time it was queued */
static void qmgr_job_link(QMGR_JOB *job)
{
QMGR_MESSAGE *message = job->message;
QMGR_JOB *prev,
*next,
- *unread;
+ *list_prev,
+ *list_next,
+ *unread,
+ *current;
int delay;
- unread = transport->job_next_unread;
+ /*
+ * Sanity checks.
+ */
+ if (job->stack_level >= 0)
+ msg_panic("qmgr_job_link: already on the job lists (%d)", job->stack_level);
/*
- * This may look inefficient but under normal operation it is expected
- * that the loop will stop right away, resulting in normal list append
+ * Traverse the time list and the scheduler list from the end and stop
+ * when we found job older than the one beeing linked.
+ *
+ * During the traversals keep track if we have come accross either the
+ * current job or the first unread job on the job list. If this is the
+ * case, these pointers will be adjusted below as required.
+ *
+ * Although both lists are exactly the same when only jobs on the stack
+ * level zero are considered, it's easier to traverse them separately.
+ * Otherwise it's impossible to keep track of the current job pointer
+ * effectively.
+ *
+ * This may look inefficient but under normal operation it is expected that
+ * the loops will stop right away, resulting in normal list appends
* below. However, this code is necessary for reviving retired jobs and
* for jobs which are created long after the first chunk of recipients
* was read in-core (either of these can happen only for multi-transport
* messages).
- *
- * In case this is found unsatisfactory one day, it's possible to deploy
- * some smarter technique (using some form of lookup trees perhaps).
*/
+ current = transport->job_current;
for (next = 0, prev = transport->job_list.prev; prev;
next = prev, prev = prev->transport_peers.prev) {
+ if (prev->stack_parent == 0) {
+ delay = message->queued_time - prev->message->queued_time;
+ if (delay >= 0)
+ break;
+ }
+ if (current == prev)
+ current = 0;
+ }
+ list_prev = prev;
+ list_next = next;
+
+ unread = transport->job_next_unread;
+ for (next = 0, prev = transport->job_bytime.prev; prev;
+ next = prev, prev = prev->time_peers.prev) {
delay = message->queued_time - prev->message->queued_time;
if (delay >= 0)
break;
}
/*
- * Don't link the new job in front of the first job on the job list if
- * that job was already used for the regular delivery. This seems like a
- * subtle difference but it helps many invariants used at various other
- * places to remain true.
+ * Link the job into the proper place on the job lists and mark it so we
+ * know it has been linked.
*/
- if (prev == 0 && next != 0 && next->slots_used != 0) {
- prev = next;
- next = next->transport_peers.next;
-
- /*
- * The following is not currently necessary but is done anyway for
- * the sake of consistency.
- */
- if (prev == transport->job_next_unread)
- unread = prev;
- }
+ job->stack_level = 0;
+ QMGR_LIST_LINK(transport->job_list, list_prev, job, list_next, transport_peers);
+ QMGR_LIST_LINK(transport->job_bytime, prev, job, next, time_peers);
/*
- * Link the job into the proper place on the job list.
+ * Update the current job pointer if necessary.
*/
- job->transport_peers.prev = prev;
- job->transport_peers.next = next;
- if (prev != 0)
- prev->transport_peers.next = job;
- else
- transport->job_list.next = job;
- if (next != 0)
- next->transport_peers.prev = job;
- else
- transport->job_list.prev = job;
+ if (current == 0)
+ transport->job_current = job;
/*
* Update the pointer to the first unread job on the job list and steal
QMGR_JOB *job;
/*
- * Try finding an existing job and revive it if it was already retired.
- * Create a new job for this transport/message combination otherwise.
+ * Try finding an existing job, reviving it if it was already retired.
+ * Create a new job for this transport/message combination otherwise. In
+ * either case, the job ends linked on the job lists.
*/
- if ((job = qmgr_job_find(message, transport)) != 0) {
- if (job->stack_level < 0) {
- job->stack_level = 0;
- qmgr_job_link(job);
- }
- } else {
+ if ((job = qmgr_job_find(message, transport)) == 0)
job = qmgr_job_create(message, transport);
+ if (job->stack_level < 0)
qmgr_job_link(job);
- }
/*
- * Reset the candidate cache because of the new expected recipients.
+ * Reset the candidate cache because of the new expected recipients. Make
+ * sure the job is not marked as a blocker for the same reason. Note that
+ * this can result in having a non-blocker followed by more blockers.
+ * Consequently, we can't just update the current job pointer, we have to
+ * reset it. Fortunately qmgr_job_entry_select() will easily deal with
+ * this and will lookup the real current job for us.
*/
RESET_CANDIDATE_CACHE(transport);
-
+ if (IS_BLOCKER(job, transport)) {
+ job->blocker_tag = 0;
+ transport->job_current = transport->job_list.next;
+ }
return (job);
}
-/* qmgr_job_move_limits - move unused recipient slots to the next job */
+/* qmgr_job_move_limits - move unused recipient slots to the next unread job */
void qmgr_job_move_limits(QMGR_JOB *job)
{
/*
* Find next unread job on the job list if necessary. Cache it for later.
- * This makes the amortized efficiency of this routine O(1) per job.
+ * This makes the amortized efficiency of this routine O(1) per job. Note
+ * that we use the time list whose ordering doesn't change over time.
*/
if (job == next) {
- for (next = next->transport_peers.next; next; next = next->transport_peers.next)
+ for (next = next->time_peers.next; next; next = next->time_peers.next)
if (next->message->rcpt_offset != 0)
break;
transport->job_next_unread = next;
/*
* Transfer the unused recipient slots back to the transport pool and to
* the next not-fully-read job. Job's message limits are adjusted
- * accordingly.
+ * accordingly. Note that the transport pool can be negative if we used
+ * some of the rcpt_per_stack slots.
*/
if (rcpt_unused > 0) {
job->rcpt_limit -= rcpt_unused;
}
}
-/* qmgr_job_retire - remove the job from the job list while waiting for recipients to deliver */
+/* qmgr_job_parent_gone - take care of orphaned stack children */
-static void qmgr_job_retire(QMGR_JOB *job)
+static void qmgr_job_parent_gone(QMGR_JOB *job, QMGR_JOB *parent)
{
- char *myname = "qmgr_job_retire";
- QMGR_TRANSPORT *transport = job->transport;
+ QMGR_JOB *child;
- if (msg_verbose)
- msg_info("%s: %s", myname, job->message->queue_id);
+ while ((child = job->stack_children.next) != 0) {
+ QMGR_LIST_UNLINK(job->stack_children, QMGR_JOB *, child, stack_siblings);
+ if (parent != 0)
+ QMGR_LIST_APPEND(parent->stack_children, child, stack_siblings);
+ child->stack_parent = parent;
+ }
+}
+
+/* qmgr_job_unlink - unlink the job from the job lists */
+
+static void qmgr_job_unlink(QMGR_JOB *job)
+{
+ char *myname = "qmgr_job_unlink";
+ QMGR_TRANSPORT *transport = job->transport;
/*
* Sanity checks.
*/
if (job->stack_level != 0)
msg_panic("%s: non-zero stack level (%d)", myname, job->stack_level);
+ if (job->stack_parent != 0)
+ msg_panic("%s: parent present", myname);
+ if (job->stack_siblings.next != 0)
+ msg_panic("%s: siblings present", myname);
/*
- * Make sure this job is not cached as the next unread job for this
- * transport. The qmgr_entry_done() will make sure that the slots donated
- * by this job are moved back to the transport pool as soon as possible.
+ * Make sure that children of job on zero stack level are informed that
+ * their parent is gone too.
*/
- qmgr_job_move_limits(job);
+ qmgr_job_parent_gone(job, 0);
+
+ /*
+ * Update the current job pointer if necessary.
+ */
+ if (transport->job_current == job)
+ transport->job_current = job->transport_peers.next;
/*
* Invalidate the candidate selection cache if necessary.
*/
if (job == transport->candidate_cache
- || (transport->job_stack.next == 0 && job == transport->job_list.next))
+ || job == transport->candidate_cache_current)
RESET_CANDIDATE_CACHE(transport);
/*
- * Remove the job from the job list and mark it as retired.
+ * Remove the job from the job lists and mark it as unlinked.
*/
QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ QMGR_LIST_UNLINK(transport->job_bytime, QMGR_JOB *, job, time_peers);
job->stack_level = -1;
}
+/* qmgr_job_retire - remove the job from the job lists while waiting for recipients to deliver */
+
+static void qmgr_job_retire(QMGR_JOB *job)
+{
+ if (msg_verbose)
+ msg_info("qmgr_job_retire: %s", job->message->queue_id);
+
+ /*
+ * Pop the job from the job stack if necessary.
+ */
+ if (job->stack_level > 0)
+ qmgr_job_pop(job);
+
+ /*
+ * Make sure this job is not cached as the next unread job for this
+ * transport. The qmgr_entry_done() will make sure that the slots donated
+ * by this job are moved back to the transport pool as soon as possible.
+ */
+ qmgr_job_move_limits(job);
+
+ /*
+ * Remove the job from the job lists. Note that it remains on the message
+ * job list, though, and that it can be revived by using
+ * qmgr_job_obtain(). Also note that the available slot counter is left
+ * intact.
+ */
+ qmgr_job_unlink(job);
+}
+
/* qmgr_job_free - release the job structure */
void qmgr_job_free(QMGR_JOB *job)
msg_panic("%s: non-zero recipient count (%d)", myname, job->rcpt_count);
/*
- * Remove the job from the job stack if necessary.
+ * Pop the job from the job stack if necessary.
*/
if (job->stack_level > 0)
qmgr_job_pop(job);
msg_panic("%s: recipient slots leak (%d)", myname, job->rcpt_limit);
/*
- * Invalidate the candidate selection cache if necessary.
- */
- if (job == transport->candidate_cache
- || (transport->job_stack.next == 0 && job == transport->job_list.next))
- RESET_CANDIDATE_CACHE(transport);
-
- /*
- * Unlink and discard the structure. Check if the job is still on the
- * transport job list or if it was already retired before unlinking it.
+ * Unlink and discard the structure. Check if the job is still linked on
+ * the job lists or if it was already retired before unlinking it.
*/
if (job->stack_level >= 0)
- QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ qmgr_job_unlink(job);
QMGR_LIST_UNLINK(message->job_list, QMGR_JOB *, job, message_peers);
htable_delete(transport->job_byname, message->queue_id, (void (*) (char *)) 0);
htable_free(job->peer_byname, (void (*) (char *)) 0);
/* qmgr_job_count_slots - maintain the delivery slot counters */
-static void qmgr_job_count_slots(QMGR_JOB *current, QMGR_JOB *job)
+static void qmgr_job_count_slots(QMGR_JOB *job)
{
/*
* Count the number of delivery slots used during the delivery of the
* selected job. Also count the number of delivery slots available for
- * preemption.
+ * its preemption.
*
- * However, suppress any slot counting if we didn't start regular delivery
- * of the selected job yet.
+ * Despite its trivial look, this is one of the key parts of the theory
+ * behind this preempting scheduler.
*/
- if (job == current || job->slots_used > 0) {
- job->slots_used++;
- job->slots_available++;
- }
+ job->slots_available++;
+ job->slots_used++;
/*
- * If the selected job is not the current job, its chance to be chosen by
- * qmgr_job_candidate() has slightly changed. If we would like to make
- * the candidate cache completely transparent, we should invalidate it
- * now.
+ * If the selected job is not the original current job, reset the
+ * candidate cache because the change above have slightly increased the
+ * chance of this job becoming a candidate next time.
*
- * However, this case should usually happen only at "end of current job"
- * phase, when it's unlikely that the current job can be preempted
- * anyway. And because it's likely to happen quite often then, we
- * intentionally don't reset the cache, to safe some cycles. Furthermore,
- * the cache times out every second anyway.
+ * Don't expect that the change of the current jobs this turn will render
+ * the candidate cache invalid the next turn - it can happen that the
+ * next turn the original current job will be selected again and the
+ * cache would be considered valid in such case.
*/
-#if 0
- if (job != current)
+ if (job != job->transport->candidate_cache_current)
RESET_CANDIDATE_CACHE(job->transport);
-#endif
}
/* qmgr_job_candidate - find best job candidate for preempting given job */
* Fetch the result directly from the cache if the cache is still valid.
*
* Note that we cache negative results too, so the cache must be invalidated
- * by resetting the cache time, not the candidate pointer itself.
+ * by resetting the cache time or current job pointer, not the candidate
+ * pointer itself.
*/
- if (transport->candidate_cache_time == now)
+ if (transport->candidate_cache_current == current
+ && transport->candidate_cache_time == now)
return (transport->candidate_cache);
/*
* score. In addition to jobs which don't meet the max_slots limit, skip
* also jobs which don't have any selectable entries at the moment.
*
+ * Instead of traversing the whole job list we traverse it just from the
+ * current job forward. This has several advantages. First, we skip some
+ * of the blocker jobs and the current job itself right away. But the
+ * really important advantage is that we are sure that we don't consider
+ * any jobs that are already stack children of the current job. Thanks to
+ * this we can easily include all encountered jobs which are leaf
+ * children of some of the preempting stacks as valid candidates. All we
+ * need to do is to make sure we do not include any of the stack parents.
+ * And, because the leaf children are not ordered by the time since
+ * queued, we have to exclude them from the early loop end test.
+ *
* By the way, the selection is reasonably resistant to OS time warping,
* too.
*
* anyway.
*/
if (max_slots > 0) {
- for (job = transport->job_list.next; job; job = job->transport_peers.next) {
- if (job->stack_level != 0 || job == current)
+ for (job = current->transport_peers.next; job; job = job->transport_peers.next) {
+ if (job->stack_children.next != 0 || IS_BLOCKER(job, transport))
continue;
max_total_entries = MAX_ENTRIES(job);
max_needed_entries = max_total_entries - job->selected_entries;
/*
* Stop early if the best score is as good as it can get.
*/
- if (delay <= best_score)
+ if (delay <= best_score && job->stack_level == 0)
break;
}
}
* Cache the result for later use.
*/
transport->candidate_cache = best_job;
+ transport->candidate_cache_current = current;
transport->candidate_cache_time = now;
return (best_job);
{
char *myname = "qmgr_job_preempt";
QMGR_TRANSPORT *transport = current->transport;
- QMGR_JOB *job;
+ QMGR_JOB *job,
+ *prev;
+ int expected_slots;
int rcpt_slots;
/*
*/
if (job == current)
msg_panic("%s: attempt to preempt itself", myname);
- if (job->stack_level != 0)
+ if (job->stack_children.next != 0)
msg_panic("%s: already on the job stack (%d)", myname, job->stack_level);
+ if (job->stack_level < 0)
+ msg_panic("%s: not on the job list (%d)", myname, job->stack_level);
/*
* Check if there is enough available delivery slots accumulated to
*
* The slot loaning scheme improves the average message response time. Note
* that the loan only allows the preemption happen earlier, though. It
- * doesn't affect how many slots have to be "paid" - the full number of
- * slots required has to be accumulated later before next preemption on
- * the same stack level can happen in either case.
- */
- if (current->slots_available / transport->slot_cost
- + transport->slot_loan
- < (MAX_ENTRIES(job) - job->selected_entries)
- * transport->slot_loan_factor / 100.0)
+ * doesn't affect how many slots have to be "paid" - in either case the
+ * full number of slots required has to be accumulated later before the
+ * current job can be preempted again.
+ */
+ expected_slots = MAX_ENTRIES(job) - job->selected_entries;
+ if (current->slots_available / transport->slot_cost + transport->slot_loan
+ < expected_slots * transport->slot_loan_factor / 100.0)
return (current);
/*
* Preempt the current job.
+ *
+ * This involves placing the selected candidate in front of the current job
+ * on the job list and updating the stack parent/child/sibling pointers
+ * appropriately. But first we need to make sure that the candidate is
+ * taken from its previous job stack which it might be top of.
*/
- QMGR_LIST_PREPEND(transport->job_stack, job, stack_peers);
+ if (job->stack_level > 0)
+ qmgr_job_pop(job);
+ QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ prev = current->transport_peers.prev;
+ QMGR_LIST_LINK(transport->job_list, prev, job, current, transport_peers);
+ job->stack_parent = current;
+ QMGR_LIST_APPEND(current->stack_children, job, stack_siblings);
job->stack_level = current->stack_level + 1;
+ /*
+ * Update the current job pointer and explicitly reset the candidate
+ * cache.
+ */
+ transport->job_current = job;
+ RESET_CANDIDATE_CACHE(transport);
+
+ /*
+ * Since the single job can be preempted by several jobs at the same
+ * time, we have to adjust the available slot count now to prevent using
+ * the same slots multiple times. To do that we subtract the number of
+ * slots the preempting job will supposedly use. This number will be
+ * corrected later when that job is popped from the stack to reflect the
+ * number of slots really used.
+ *
+ * As long as we don't need to keep track of how many slots were really
+ * used, we can (ab)use the slots_used counter for counting the
+ * difference between the real and expected amounts instead of the
+ * absolute amount.
+ */
+ current->slots_available -= expected_slots * transport->slot_cost;
+ job->slots_used = -expected_slots;
+
/*
* Add part of extra recipient slots reserved for preempting jobs to the
* new current job if necessary.
job->message->rcpt_limit += rcpt_slots;
transport->rcpt_unused -= rcpt_slots;
}
-
- /*
- * Candidate cache must be reset because the current job has changed
- * completely.
- */
- RESET_CANDIDATE_CACHE(transport);
-
if (msg_verbose)
- msg_info("%s: %s by %s", myname, current->message->queue_id,
- job->message->queue_id);
+ msg_info("%s: %s by %s, level %d", myname, current->message->queue_id,
+ job->message->queue_id, job->stack_level);
return (job);
}
-/* qmgr_job_pop - remove the job from the job preemption stack */
+/* qmgr_job_pop - remove the job from its job preemption stack */
static void qmgr_job_pop(QMGR_JOB *job)
{
+ char *myname = "qmgr_job_pop";
QMGR_TRANSPORT *transport = job->transport;
QMGR_JOB *parent;
if (msg_verbose)
- msg_info("qmgr_job_pop: %s", job->message->queue_id);
+ msg_info("%s: %s", myname, job->message->queue_id);
+
+ /*
+ * Sanity checks.
+ */
+ if (job->stack_level <= 0)
+ msg_panic("%s: not on the job stack (%d)", myname, job->stack_level);
/*
* Adjust the number of delivery slots available to preempt job's parent.
* preemption appeared near the end of parent delivery.
*
* For the same reason we do not adjust parent's slots_available if the
- * parent is not the original parent preempted by the selected job (i.e.,
- * the original parent job has already completed).
+ * parent is not the original parent that was preempted by this job
+ * (i.e., the original parent job has already completed).
*
- * The special case when the head of the job list was preempted and then
- * delivered before the preempting job itself is taken care of too.
- * Otherwise we would decrease available slot counter of some job that
- * was not in fact preempted yet.
+ * This is another key part of the theory behind this preempting scheduler.
*/
- if (((parent = job->stack_peers.next) != 0
- || ((parent = transport->job_list.next) != 0 && parent->slots_used > 0))
+ if ((parent = job->stack_parent) != 0
&& job->stack_level == parent->stack_level + 1)
parent->slots_available -= job->slots_used * transport->slot_cost;
/*
- * Invalidate the candidate selection cache if necessary.
+ * Remove the job from its parent's children list.
*/
- if (job == transport->job_stack.next)
- RESET_CANDIDATE_CACHE(transport);
+ if (parent != 0) {
+ QMGR_LIST_UNLINK(parent->stack_children, QMGR_JOB *, job, stack_siblings);
+ job->stack_parent = 0;
+ }
+
+ /*
+ * If there is a parent, let it adopt all those orphaned children.
+ * Otherwise at least notify the children that their parent is gone.
+ */
+ qmgr_job_parent_gone(job, parent);
/*
- * Remove the job from the job stack and reinitialize the slot counters.
+ * Put the job back to stack level zero.
*/
- QMGR_LIST_UNLINK(transport->job_stack, QMGR_JOB *, job, stack_peers);
job->stack_level = 0;
- job->slots_used = 0;
- job->slots_available = 0;
+
+ /*
+ * Here we leave the remaining work involving the proper placement on the
+ * job list to the caller. The most important reason for this is that it
+ * allows us not to look up where exactly to place the job.
+ *
+ * The caller is also made responsible for invalidating the candidate and
+ * current job caches if necessary.
+ */
+#if 0
+ QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ QMGR_LIST_LINK(transport->job_list, some_prev, job, some_next, transport_peers);
+
+ RESET_CANDIDATE_CACHE(transport);
+ if (transport->job_current == job)
+ transport->job_current = job->transport_peers.next;
+#endif
}
/* qmgr_job_peer_select - select next peer suitable for delivery */
QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *transport)
{
QMGR_JOB *job,
- *current,
*next;
QMGR_PEER *peer;
QMGR_ENTRY *entry;
/*
- * Select the "current" job.
+ * Get the current job if there is one.
*/
- if ((current = transport->job_stack.next) == 0
- && (current = transport->job_list.next) == 0)
+ if ((job = transport->job_current) == 0)
return (0);
/*
* therefore disabled too.
*/
if (transport->slot_cost >= 2)
- current = qmgr_job_preempt(current);
+ job = qmgr_job_preempt(job);
/*
- * Select next entry suitable for delivery. First check the stack of
- * preempting jobs, then the list of all remaining jobs in FIFO order.
- *
- * Note that although the loops may look inefficient, they only serve as a
- * recovery mechanism when an entry of the current job itself can't be
- * selected due peer concurrency restrictions. In most cases some entry
- * of the current job itself is selected.
+ * Select next entry suitable for delivery. In case the current job can't
+ * provide one because of the per-destination concurrency limits, we mark
+ * it as a "blocker" job and continue with the next job on the job list.
*
- * Note that both loops also take care of getting the "stall" current job
- * (job with no entries currently available) out of the way if necessary.
- * Stall jobs can appear in case of multi-transport messages whose
- * recipients don't fit in-core at once. Some jobs created by such
- * message may have only few recipients and would block the job queue
- * until all other jobs of the message are delivered. Trying to read in
- * more recipients of such jobs each selection would also break the per
- * peer recipient grouping of the other jobs. That's why we retire such
- * jobs below.
- */
- for (job = transport->job_stack.next; job; job = next) {
- next = job->stack_peers.next;
+ * Note that the loop also takes care of getting the "stall" jobs (job with
+ * no entries currently available) out of the way if necessary. Stall
+ * jobs can appear in case of multi-transport messages whose recipients
+ * don't fit in-core at once. Some jobs created by such message may have
+ * only few recipients and would stay on the job list until all other
+ * jobs of that message are delivered, blocking precious recipient slots
+ * available to this transport. Or it can happen that the job has some
+ * more entries but suddenly they all get deferred. Whatever the reason,
+ * we retire such jobs below if we happen to come across some.
+ */
+ for ( /* empty */ ; job; job = next) {
+ next = job->transport_peers.next;
+
+ /*
+ * Don't bother if the job is known to have no available entries
+ * because of the per-destination concurrency limits.
+ */
+ if (IS_BLOCKER(job, transport))
+ continue;
+
if ((peer = qmgr_job_peer_select(job)) != 0) {
+
+ /*
+ * We have found a suitable peer. Select one of its entries and
+ * adjust the delivery slot counters.
+ */
entry = qmgr_entry_select(peer);
- qmgr_job_count_slots(current, job);
+ qmgr_job_count_slots(job);
+
+ /*
+ * Remember the current job for the next time so we don't have to
+ * crawl over all those blockers again. They will be reconsidered
+ * when the concurrency limit permits.
+ */
+ transport->job_current = job;
/*
* In case we selected the very last job entry, remove the job
- * from the job stack and the job list right now.
+ * from the job lists right now.
*
* This action uses the assumption that once the job entry has been
* selected, it can be unselected only before the message ifself
* re-appear with more entries available for selection again
* (without reading in more entries from the queue file, which in
* turn invokes qmgr_job_obtain() which re-links the job back on
- * the list if necessary).
+ * the lists if necessary).
*
* Note that qmgr_job_move_limits() transfers the recipients slots
* correctly even if the job is unlinked from the job list thanks
* to the job_next_unread caching.
*/
- if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0) {
- qmgr_job_pop(job);
+ if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0)
qmgr_job_retire(job);
- }
+
+ /*
+ * Finally. Hand back the fruit of our tedious effort.
+ */
return (entry);
- } else if (job == current && !HAS_ENTRIES(job)) {
- qmgr_job_pop(job);
- qmgr_job_retire(job);
- current = next ? next : transport->job_list.next;
- }
- }
+ } else if (HAS_ENTRIES(job)) {
- /*
- * Try the regular job list if there is nothing (suitable) on the job
- * stack.
- */
- for (job = transport->job_list.next; job; job = next) {
- next = job->transport_peers.next;
- if (job->stack_level != 0)
- continue;
- if ((peer = qmgr_job_peer_select(job)) != 0) {
- entry = qmgr_entry_select(peer);
- qmgr_job_count_slots(current, job);
+ /*
+ * The job can't be selected due the concurrency limits. Mark it
+ * together with its queues so we know they are blocking the job
+ * list and they get the appropriate treatment. In particular,
+ * all blockers will be reconsidered when one of the problematic
+ * queues will accept more deliveries. And the job itself will be
+ * reconsidered if it is assigned some more entries.
+ */
+ job->blocker_tag = transport->blocker_tag;
+ for (peer = job->peer_list.next; peer; peer = peer->peers.next)
+ if (peer->entry_list.next != 0)
+ peer->queue->blocker_tag = transport->blocker_tag;
+ } else {
/*
- * In case we selected the very last job entry, remove the job
- * from the job list right away.
+ * The job is "stalled". Retire it until it either gets freed or
+ * gets more entries later.
*/
- if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0)
- qmgr_job_retire(job);
- return (entry);
- } else if (job == current && !HAS_ENTRIES(job)) {
qmgr_job_retire(job);
- current = next;
}
}
+
+ /*
+ * We have not found any entry we could use for delivery. Well, things
+ * must have changed since this transport was selected for asynchronous
+ * allocation. Never mind. Clear the current job pointer and reluctantly
+ * report back that we have failed in our task.
+ */
+ transport->job_current = 0;
return (0);
}
if (message->rcpt_offset) {
if (message->rcpt_list.len)
msg_panic("%s: recipient list not empty on recipient reload", message->queue_id);
- if (message->rcpt_limit <= message->rcpt_count)
- msg_panic("%s: no recipient slots available", message->queue_id);
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;
/*
* Compare message transport.
*/
- if ((result = strcasecmp(queue1->transport->name,
- queue2->transport->name)) != 0)
+ if ((result = strcmp(queue1->transport->name,
+ queue2->transport->name)) != 0)
return (result);
/*
- * Compare next-hop hostname.
+ * Compare (already lowercased) next-hop hostname.
*/
- if ((result = strcasecmp(queue1->name, queue2->name)) != 0)
+ if ((result = strcmp(queue1->name, queue2->name)) != 0)
return (result);
}
/*
* Compare recipient address.
*/
- return (strcasecmp(rcpt1->address, rcpt2->address));
+ return (strcmp(rcpt1->address, rcpt2->address));
}
/* qmgr_message_sort - sort message recipient addresses by domain */
char *nexthop;
int len;
-#define STREQ(x,y) (strcasecmp(x,y) == 0)
+#define STREQ(x,y) (strcmp(x,y) == 0)
#define STR vstring_str
#define LEN VSTRING_LEN
#define UPDATE(ptr,new) { myfree(ptr); ptr = mystrdup(new); }
*/
if ((at = strrchr(recipient->address, '@')) != 0
&& (at + 1)[strspn(at + 1, "[]0123456789.")] != 0
- && valid_hostname(at + 1) == 0) {
+ && valid_hostname(at + 1, DONT_GRIPE) == 0) {
qmgr_bounce_recipient(message, recipient,
"bad host/domain syntax: \"%s\"", at + 1);
continue;
* postmaster, though, but that is an RFC requirement anyway.
*/
if (strncasecmp(STR(reply.recipient), var_double_bounce_sender,
- at - STR(reply.recipient)) == 0
- && !var_double_bounce_sender[at - STR(reply.recipient)]) {
+ len) == 0
+ && !var_double_bounce_sender[len]) {
sent(message->queue_id, recipient->address,
"none", message->arrival_time, "discarded");
deliver_completed(message->fp, recipient->offset);
if (defer_xport_argv == 0)
defer_xport_argv = argv_split(var_defer_xports, " \t\r\n,");
for (cpp = defer_xport_argv->argv; *cpp; cpp++)
- if (strcasecmp(*cpp, STR(reply.transport)) == 0)
+ if (strcmp(*cpp, STR(reply.transport)) == 0)
break;
if (*cpp) {
qmgr_defer_recipient(message, recipient->address,
if (message->single_rcpt || entry == 0 || entry->queue != queue
|| !LIMIT_OK(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;
}
+
+ /*
+ * 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->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 as the
+ * number of unread recipients affecting the candidate selection might
+ * have changed considerably, let's invalidate the caches if it seems it
+ * might be of some use. It's not critical though because the cache will
+ * expire within one second anyway.
+ */
+ 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 */
*/
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++;
}
QMGR_LIST_INIT(queue->todo);
QMGR_LIST_INIT(queue->busy);
queue->reason = 0;
+ queue->blocker_tag = 0;
QMGR_LIST_APPEND(transport->queue_list, queue, peers);
htable_enter(transport->queue_byname, site, (char *) queue);
return (queue);
QMGR_LIST_INIT(transport->queue_list);
transport->job_byname = htable_create(0);
QMGR_LIST_INIT(transport->job_list);
- QMGR_LIST_INIT(transport->job_stack);
+ 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);
argv_free(hash_queue_names);
}
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
int fd;
struct stat st;
/* qmgr_bounce_recipient - bounce one message recipient */
-void qmgr_bounce_recipient(QMGR_MESSAGE *message, QMGR_RCPT * recipient,
+void qmgr_bounce_recipient(QMGR_MESSAGE *message, QMGR_RCPT *recipient,
const char *format,...)
{
va_list ap;
*/
if ((at = strrchr(recipient->address, '@')) != 0
&& (at + 1)[strspn(at + 1, "[]0123456789.")] != 0
- && valid_hostname(at + 1) == 0) {
+ && valid_hostname(at + 1, DONT_GRIPE) == 0) {
qmgr_bounce_recipient(message, recipient,
"bad host/domain syntax: \"%s\"", at + 1);
continue;
* postmaster, though, but that is an RFC requirement anyway.
*/
if (strncasecmp(STR(reply.recipient), var_double_bounce_sender,
- at - STR(reply.recipient)) == 0
- && !var_double_bounce_sender[at - STR(reply.recipient)]) {
+ len) == 0
+ && !var_double_bounce_sender[len]) {
sent(message->queue_id, recipient->address,
"none", message->arrival_time, "discarded");
deliver_completed(message->fp, recipient->offset);
smtpd_check.o: ../../include/mail_addr_find.h
smtpd_check.o: smtpd.h
smtpd_check.o: ../../include/mail_stream.h
+smtpd_check.o: smtpd_sasl_glue.h
smtpd_check.o: smtpd_check.h
smtpd_peer.o: smtpd_peer.c
smtpd_peer.o: ../../include/sys_defs.h
}
if (!ISALNUM(argv[1].strval[0]))
argv[1].strval++;
- if (!valid_hostname(argv[1].strval)) {
+ if (!valid_hostname(argv[1].strval, DONT_GRIPE)) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Error: invalid parameter syntax");
return (-1);
/*
* Validate the address.
*/
- if (!valid_hostaddr(test_addr))
+ if (!valid_hostaddr(test_addr, DONT_GRIPE))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: invalid ip address",
var_bad_name_code, reply_name, reply_class);
/*
* Validate the hostname.
*/
- if (!valid_hostname(test_name))
+ if (!valid_hostname(test_name, DONT_GRIPE))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Invalid name",
var_bad_name_code, reply_name, reply_class);
/*
* Validate the hostname.
*/
- if (!valid_hostname(test_name) || !strchr(test_name, '.'))
+ if (!valid_hostname(test_name, DONT_GRIPE) || !strchr(test_name, '.'))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: need fully-qualified hostname",
var_non_fqdn_code, reply_name, reply_class);
/*
* Validate the domain.
*/
- if (!*test_dom || !valid_hostname(test_dom) || !strchr(test_dom, '.'))
+ if (!*test_dom || !valid_hostname(test_dom, DONT_GRIPE) || !strchr(test_dom, '.'))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: need fully-qualified address",
var_non_fqdn_code, reply_name, reply_class);
if (hp == 0) {
state->name = mystrdup("unknown");
state->peer_code = (h_errno == TRY_AGAIN ? 4 : 5);
- } else if (!valid_hostname(hp->h_name)) {
+ } else if (!valid_hostname(hp->h_name, DONT_GRIPE)) {
state->name = mystrdup("unknown");
state->peer_code = 5;
} else {
#include <stdlib.h>
#include <lber.h>
#include <ldap.h>
+#include <string.h>
/* Utility library. */
* load on the LDAP server.
*/
if (dict_ldap->domain) {
- if (strrchr(name, '@') != 0) {
- if (match_list_match(dict_ldap->domain, (char *) strrchr(name, '@') + 1) == 0) {
- if (msg_verbose)
- msg_info("%s: domain of %s not found in domain list", myname,
- name);
- return (0);
- }
+ char *p=strrchr(name,'@');
+ if (p != 0)
+ p=p+1;
+ else
+ p=name;
+ if (match_list_match(dict_ldap->domain, p) == 0) {
+ if (msg_verbose)
+ msg_info("%s: domain of %s not found in domain list", myname,
+ name);
+ return (0);
}
}
if (gethostname(namebuf, sizeof(namebuf)) < 0)
msg_fatal("gethostname: %m");
namebuf[MAXHOSTNAMELEN] = 0;
- if (valid_hostname(namebuf) == 0)
+ if (valid_hostname(namebuf, DO_GRIPE) == 0)
msg_fatal("unable to use my own hostname");
my_host_name = mystrdup(namebuf);
}
/* SYNOPSIS
/* #include <valid_hostname.h>
/*
-/* int valid_hostname(name)
+/* int valid_hostname(name, gripe)
/* const char *name;
+/* int gripe;
/*
-/* int valid_hostaddr(addr)
+/* int valid_hostaddr(addr, gripe)
/* const char *addr;
+/* int gripe;
/* DESCRIPTION
/* valid_hostname() scrutinizes a hostname: the name should be no
/* longer than VALID_HOSTNAME_LEN characters, should contain only
/*
/* valid_hostaddr() requirs that the input is a valid string
/* representation of an internet network address.
+/*
+/* These routines operate silently unless the gripe parameter
+/* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE
+/* provide suitable constants.
/* DIAGNOSTICS
/* Both functions return zero if they disagree with the input.
/* SEE ALSO
/* valid_hostname - screen out bad hostnames */
-int valid_hostname(const char *name)
+int valid_hostname(const char *name, int gripe)
{
char *myname = "valid_hostname";
const char *cp;
* Trivial cases first.
*/
if (*name == 0) {
- msg_warn("%s: empty hostname", myname);
+ if (gripe)
+ msg_warn("%s: empty hostname", myname);
return (0);
}
label_count++;
label_length++;
if (label_length > VALID_LABEL_LEN) {
- msg_warn("%s: hostname label too long: %.100s", myname, name);
+ if (gripe)
+ msg_warn("%s: hostname label too long: %.100s", myname, name);
return (0);
}
if (!ISDIGIT(ch))
non_numeric = 1;
} else if (ch == '.') {
if (label_length == 0 || cp[1] == 0) {
- msg_warn("%s: misplaced delimiter: %.100s", myname, name);
+ if (gripe)
+ msg_warn("%s: misplaced delimiter: %.100s", myname, name);
return (0);
}
label_length = 0;
} else if (ch == '-') {
label_length++;
if (label_length == 1 || cp[1] == 0 || cp[1] == '.') {
- msg_warn("%s: misplaced hyphen: %.100s", myname, name);
+ if (gripe)
+ msg_warn("%s: misplaced hyphen: %.100s", myname, name);
return (0);
}
} else {
- msg_warn("%s: invalid character %d(decimal): %.100s",
- myname, ch, name);
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal): %.100s",
+ myname, ch, name);
return (0);
}
}
if (non_numeric == 0) {
- msg_warn("%s: numeric hostname: %.100s", myname, name);
+ if (gripe)
+ msg_warn("%s: numeric hostname: %.100s", myname, name);
/* NOT: return (0); this confuses users of the DNS client */
}
if (cp - name > VALID_HOSTNAME_LEN) {
- msg_warn("%s: bad length %d for %.100s...",
- myname, (int) (cp - name), name);
+ if (gripe)
+ msg_warn("%s: bad length %d for %.100s...",
+ myname, (int) (cp - name), name);
return (0);
}
return (1);
/* valid_hostaddr - test dotted quad string for correctness */
-int valid_hostaddr(const char *addr)
+int valid_hostaddr(const char *addr, int gripe)
{
const char *cp;
char *myname = "valid_hostaddr";
* Trivial cases first.
*/
if (*addr == 0) {
- msg_warn("%s: empty address", myname);
+ if (gripe)
+ msg_warn("%s: empty address", myname);
return (0);
}
byte_val *= 10;
byte_val += ch - '0';
if (byte_val > 255) {
- msg_warn("%s: invalid octet value: %.100s", myname, addr);
+ if (gripe)
+ msg_warn("%s: invalid octet value: %.100s", myname, addr);
return (0);
}
} else if (ch == '.') {
if (in_byte == 0 || cp[1] == 0) {
- msg_warn("%s: misplaced dot: %.100s", myname, addr);
+ if (gripe)
+ msg_warn("%s: misplaced dot: %.100s", myname, addr);
return (0);
}
if ((byte_count == 1 && byte_val == 0)) {
- msg_warn("%s: bad initial octet value: %.100s", myname, addr);
+ if (gripe)
+ msg_warn("%s: bad initial octet value: %.100s", myname, addr);
return (0);
}
in_byte = 0;
} else {
- msg_warn("%s: invalid character %d(decimal): %.100s",
- myname, ch, addr);
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal): %.100s",
+ myname, ch, addr);
return (0);
}
}
if (byte_count != BYTES_NEEDED) {
- msg_warn("%s: invalid octet count: %.100s", myname, addr);
+ if (gripe)
+ msg_warn("%s: invalid octet count: %.100s", myname, addr);
return (0);
}
return (1);
while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
msg_info("testing: \"%s\"", vstring_str(buffer));
- valid_hostname(vstring_str(buffer));
- valid_hostaddr(vstring_str(buffer));
+ valid_hostname(vstring_str(buffer), DO_GRIPE);
+ valid_hostaddr(vstring_str(buffer), DO_GRIPE);
}
exit(0);
}
#define VALID_HOSTNAME_LEN 255 /* RFC 1035 */
#define VALID_LABEL_LEN 63 /* RFC 1035 */
-extern int valid_hostname(const char *);
-extern int valid_hostaddr(const char *);
+#define DONT_GRIPE 0
+#define DO_GRIPE 1
+
+extern int valid_hostname(const char *, int);
+extern int valid_hostaddr(const char *, int);
/* LICENSE
/* .ad
--- /dev/null
+../../.indent.pro
\ No newline at end of file
--- /dev/null
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 4 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
--- /dev/null
+SHELL = /bin/sh
+SRCS = virtual.c mailbox.c recipient.c deliver_attr.c maildir.c unknown.c
+OBJS = virtual.o mailbox.o recipient.o deliver_attr.o maildir.o unknown.o
+HDRS = virtual.h
+TESTSRC =
+WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
+ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \
+ -Wunused
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) -I..
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+PROG = virtual
+TESTPROG=
+INC_DIR = ../../include
+LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libutil.a $(AUXLIBS)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+Makefile: Makefile.in
+ (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs; cat $?) >$@
+
+test: $(TESTPROG)
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \
+ done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @make -f Makefile.in Makefile
+
+# do not edit below this line - it is generated by 'make depend'
+deliver_attr.o: deliver_attr.c
+deliver_attr.o: ../../include/sys_defs.h
+deliver_attr.o: ../../include/msg.h
+deliver_attr.o: ../../include/vstream.h
+deliver_attr.o: ../../include/vbuf.h
+deliver_attr.o: virtual.h
+deliver_attr.o: ../../include/vstring.h
+deliver_attr.o: ../../include/deliver_request.h
+deliver_attr.o: ../../include/recipient_list.h
+deliver_attr.o: ../../include/maps.h
+deliver_attr.o: ../../include/dict.h
+deliver_attr.o: ../../include/argv.h
+deliver_attr.o: ../../include/mbox_conf.h
+mailbox.o: mailbox.c
+mailbox.o: ../../include/sys_defs.h
+mailbox.o: ../../include/msg.h
+mailbox.o: ../../include/vstring.h
+mailbox.o: ../../include/vbuf.h
+mailbox.o: ../../include/vstream.h
+mailbox.o: ../../include/mymalloc.h
+mailbox.o: ../../include/stringops.h
+mailbox.o: ../../include/set_eugid.h
+mailbox.o: ../../include/mail_copy.h
+mailbox.o: ../../include/mbox_open.h
+mailbox.o: ../../include/safe_open.h
+mailbox.o: ../../include/defer.h
+mailbox.o: ../../include/bounce.h
+mailbox.o: ../../include/sent.h
+mailbox.o: ../../include/mail_params.h
+mailbox.o: virtual.h
+mailbox.o: ../../include/deliver_request.h
+mailbox.o: ../../include/recipient_list.h
+mailbox.o: ../../include/maps.h
+mailbox.o: ../../include/dict.h
+mailbox.o: ../../include/argv.h
+mailbox.o: ../../include/mbox_conf.h
+maildir.o: maildir.c
+maildir.o: ../../include/sys_defs.h
+maildir.o: ../../include/msg.h
+maildir.o: ../../include/mymalloc.h
+maildir.o: ../../include/stringops.h
+maildir.o: ../../include/vstring.h
+maildir.o: ../../include/vbuf.h
+maildir.o: ../../include/vstream.h
+maildir.o: ../../include/make_dirs.h
+maildir.o: ../../include/set_eugid.h
+maildir.o: ../../include/get_hostname.h
+maildir.o: ../../include/sane_fsops.h
+maildir.o: ../../include/mail_copy.h
+maildir.o: ../../include/bounce.h
+maildir.o: ../../include/defer.h
+maildir.o: ../../include/sent.h
+maildir.o: ../../include/mail_params.h
+maildir.o: virtual.h
+maildir.o: ../../include/deliver_request.h
+maildir.o: ../../include/recipient_list.h
+maildir.o: ../../include/maps.h
+maildir.o: ../../include/dict.h
+maildir.o: ../../include/argv.h
+maildir.o: ../../include/mbox_conf.h
+recipient.o: recipient.c
+recipient.o: ../../include/sys_defs.h
+recipient.o: ../../include/msg.h
+recipient.o: ../../include/mymalloc.h
+recipient.o: ../../include/stringops.h
+recipient.o: ../../include/vstring.h
+recipient.o: ../../include/vbuf.h
+recipient.o: ../../include/bounce.h
+recipient.o: virtual.h
+recipient.o: ../../include/vstream.h
+recipient.o: ../../include/deliver_request.h
+recipient.o: ../../include/recipient_list.h
+recipient.o: ../../include/maps.h
+recipient.o: ../../include/dict.h
+recipient.o: ../../include/argv.h
+recipient.o: ../../include/mbox_conf.h
+unknown.o: unknown.c
+unknown.o: ../../include/sys_defs.h
+unknown.o: ../../include/msg.h
+unknown.o: ../../include/bounce.h
+unknown.o: virtual.h
+unknown.o: ../../include/vstream.h
+unknown.o: ../../include/vbuf.h
+unknown.o: ../../include/vstring.h
+unknown.o: ../../include/deliver_request.h
+unknown.o: ../../include/recipient_list.h
+unknown.o: ../../include/maps.h
+unknown.o: ../../include/dict.h
+unknown.o: ../../include/argv.h
+unknown.o: ../../include/mbox_conf.h
+virtual.o: virtual.c
+virtual.o: ../../include/sys_defs.h
+virtual.o: ../../include/msg.h
+virtual.o: ../../include/vstring.h
+virtual.o: ../../include/vbuf.h
+virtual.o: ../../include/vstream.h
+virtual.o: ../../include/iostuff.h
+virtual.o: ../../include/set_eugid.h
+virtual.o: ../../include/dict.h
+virtual.o: ../../include/argv.h
+virtual.o: ../../include/mail_queue.h
+virtual.o: ../../include/recipient_list.h
+virtual.o: ../../include/deliver_request.h
+virtual.o: ../../include/deliver_completed.h
+virtual.o: ../../include/mail_params.h
+virtual.o: ../../include/mail_conf.h
+virtual.o: ../../include/mail_server.h
+virtual.o: virtual.h
+virtual.o: ../../include/maps.h
+virtual.o: ../../include/mbox_conf.h
--- /dev/null
+/*++
+/* NAME
+/* deliver_attr 3
+/* SUMMARY
+/* initialize message delivery attributes
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* void deliver_attr_init(attrp)
+/* DELIVER_ATTR *attrp;
+/*
+/* void deliver_attr_dump(attrp)
+/* DELIVER_ATTR *attrp;
+/* DESCRIPTION
+/* deliver_attr_init() initializes a structure with message delivery
+/* attributes to a known initial state (all zeros).
+/*
+/* deliver_attr_dump() logs the contents of the given attribute list.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_attr_init - set message delivery attributes to all-zero state */
+
+void deliver_attr_init(DELIVER_ATTR *attrp)
+{
+ attrp->level = 0;
+ attrp->fp = 0;
+ attrp->queue_name = 0;
+ attrp->queue_id = 0;
+ attrp->offset = 0;
+ attrp->sender = 0;
+ attrp->recipient = 0;
+ attrp->user = 0;
+ attrp->delivered = 0;
+ attrp->relay = 0;
+}
+
+/* deliver_attr_dump - log message delivery attributes */
+
+void deliver_attr_dump(DELIVER_ATTR *attrp)
+{
+ msg_info("level: %d", attrp->level);
+ msg_info("path: %s", VSTREAM_PATH(attrp->fp));
+ msg_info("fp: 0x%lx", (long) attrp->fp);
+ msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null");
+ msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null");
+ msg_info("offset: %ld", attrp->offset);
+ msg_info("sender: %s", attrp->sender ? attrp->sender : "null");
+ msg_info("recipient: %s", attrp->recipient ? attrp->recipient : "null");
+ msg_info("user: %s", attrp->user ? attrp->user : "null");
+ msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null");
+ msg_info("relay: %s", attrp->relay ? attrp->relay : "null");
+}
--- /dev/null
+/*++
+/* NAME
+/* mailbox 3
+/* SUMMARY
+/* mailbox delivery
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_mailbox(state, usr_attr, statusp)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* int *statusp;
+/* DESCRIPTION
+/* deliver_mailbox() delivers to UNIX-style mailbox or to maildir.
+/*
+/* A zero result means that the named user was not found.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* .IP usr_attr
+/* Attributes describing user rights and mailbox location.
+/* .IP statusp
+/* Delivery status: see below.
+/* DIAGNOSTICS
+/* The message delivery status is non-zero when delivery should be tried
+/* again.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <set_eugid.h>
+
+/* Global library. */
+
+#include <mail_copy.h>
+#include <mbox_open.h>
+#include <defer.h>
+#include <sent.h>
+#include <mail_params.h>
+
+#ifndef EDQUOT
+#define EDQUOT EFBIG
+#endif
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+#define YES 1
+#define NO 0
+
+/* deliver_mailbox_file - deliver to recipient mailbox */
+
+static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ char *myname = "deliver_mailbox_file";
+ VSTRING *why;
+ MBOX *mp;
+ int status;
+ int copy_flags;
+ long end;
+ struct stat st;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Initialize. Assume the operation will fail. Set the delivered
+ * attribute to reflect the final recipient.
+ */
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
+ state.msg_attr.delivered = state.msg_attr.recipient;
+ status = -1;
+ why = vstring_alloc(100);
+
+ /*
+ * Lock the mailbox and open/create the mailbox file.
+ *
+ * Write the file as the recipient, so that file quota work.
+ */
+ copy_flags = MAIL_COPY_MBOX;
+
+ set_eugid(usr_attr.uid, usr_attr.gid);
+ mp = mbox_open(usr_attr.mailbox, O_APPEND | O_WRONLY | O_CREAT,
+ S_IRUSR | S_IWUSR, &st, -1, -1,
+ virtual_mbox_lock_mask, why);
+ if (mp != 0) {
+ if (S_ISREG(st.st_mode) == 0) {
+ vstream_fclose(mp->fp);
+ vstring_sprintf(why, "destination is not a regular file");
+ errno = 0;
+ } else {
+ end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END);
+ status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
+ copy_flags, "\n", why);
+ }
+ mbox_release(mp);
+ }
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * As the mail system, bounce, defer delivery, or report success.
+ */
+ if (status != 0)
+ status = (errno == EDQUOT || errno == EFBIG ?
+ bounce_append : defer_append)
+ (BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "mailbox %s: %s", usr_attr.mailbox, vstring_str(why));
+ else
+ sent(SENT_ATTR(state.msg_attr), "mailbox");
+
+ vstring_free(why);
+ return (status);
+}
+
+/* deliver_mailbox - deliver to recipient mailbox */
+
+int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
+{
+ char *myname = "deliver_mailbox";
+ const char *mailbox_res;
+ const char *uid_res;
+ const char *gid_res;
+ long n;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Sanity check.
+ */
+ if (*var_virt_mailbox_base != '/')
+ msg_fatal("do not specify relative pathname: %s = %s",
+ VAR_VIRT_MAILBOX_BASE, var_virt_mailbox_base);
+
+ /*
+ * Look up the mailbox location. Bounce if not found, defer in case of
+ * trouble.
+ */
+ mailbox_res = maps_find(virtual_mailbox_maps, state.msg_attr.user, 0);
+ if (mailbox_res == 0) {
+ if (dict_errno == 0)
+ return (NO);
+
+ *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "%s: lookup %s: %m",
+ virtual_mailbox_maps->title, state.msg_attr.user);
+ return (YES);
+ }
+
+ /*
+ * Look up the mailbox owner rights. Defer in case of trouble.
+ */
+ if ((uid_res = maps_find(virtual_uid_maps, state.msg_attr.user, 0)) == 0) {
+ *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "recipient %s: uid not found in %s",
+ state.msg_attr.user, virtual_uid_maps->title);
+ return (YES);
+ }
+ if ((n = atol(uid_res)) < var_virt_minimum_uid) {
+ *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "recipient %s: bad uid %s in %s",
+ state.msg_attr.user, uid_res, virtual_uid_maps->title);
+ return (YES);
+ }
+ usr_attr.uid = (uid_t) n;
+
+ /*
+ * Look up the mailbox group rights. Defer in case of trouble.
+ */
+ if ((gid_res = maps_find(virtual_gid_maps, state.msg_attr.user, 0)) == 0) {
+ *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "recipient %s: gid not found in %s",
+ state.msg_attr.user, virtual_gid_maps->title);
+ return (YES);
+ }
+ if ((n = atol(gid_res)) <= 0) {
+ *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "recipient %s: bad gid %s in %s",
+ state.msg_attr.user, gid_res, virtual_gid_maps->title);
+ return (YES);
+ }
+ usr_attr.gid = (gid_t) n;
+
+ /*
+ * No early returns or we have a memory leak.
+ */
+ usr_attr.mailbox = concatenate(var_virt_mailbox_base, "/",
+ mailbox_res, (char *) 0);
+ if (msg_verbose)
+ msg_info("%s[%d]: set user_attr: %s, uid = %d, gid = %d",
+ myname, state.level,
+ usr_attr.mailbox, usr_attr.uid, usr_attr.gid);
+
+ /*
+ * Deliver to mailbox or to external command.
+ */
+#define LAST_CHAR(s) (s[strlen(s) - 1])
+
+ if (LAST_CHAR(usr_attr.mailbox) == '/')
+ *statusp = deliver_maildir(state, usr_attr);
+ else
+ *statusp = deliver_mailbox_file(state, usr_attr);
+
+ /*
+ * Cleanup.
+ */
+ myfree(usr_attr.mailbox);
+ return (YES);
+}
--- /dev/null
+/*++
+/* NAME
+/* maildir 3
+/* SUMMARY
+/* delivery to maildir
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_maildir(state, usr_attr)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* DESCRIPTION
+/* deliver_maildir() delivers a message to a qmail-style maildir.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, recipient and more.
+/* .IP usr_attr
+/* Attributes describing user rights and environment information.
+/* DIAGNOSTICS
+/* deliver_maildir() always succeeds or it bounces the message.
+/* SEE ALSO
+/* bounce(3)
+/* 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
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <errno.h>
+
+#ifndef EDQUOT
+#define EDQUOT EFBIG
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <make_dirs.h>
+#include <set_eugid.h>
+#include <get_hostname.h>
+#include <sane_fsops.h>
+
+/* Global library. */
+
+#include <mail_copy.h>
+#include <bounce.h>
+#include <defer.h>
+#include <sent.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_maildir - delivery to maildir-style mailbox */
+
+int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ char *myname = "deliver_maildir";
+ char *newdir;
+ char *tmpdir;
+ char *curdir;
+ char *tmpfile;
+ char *newfile;
+ VSTRING *why;
+ VSTRING *buf;
+ VSTREAM *dst;
+ int status;
+ int copy_flags;
+ static int count;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Initialize. Assume the operation will fail. Set the delivered
+ * attribute to reflect the final recipient.
+ */
+ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
+ msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
+ state.msg_attr.delivered = state.msg_attr.recipient;
+ status = -1;
+ buf = vstring_alloc(100);
+ why = vstring_alloc(100);
+
+ copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_DELIVERED;
+
+ newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0);
+ tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0);
+ curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0);
+
+ /*
+ * Create and write the file as the recipient, so that file quota work.
+ * Create any missing directories on the fly. The file name is chosen
+ * according to ftp://koobera.math.uic.edu/www/proto/maildir.html:
+ *
+ * "A unique name has three pieces, separated by dots. On the left is the
+ * result of time(). On the right is the result of gethostname(). In the
+ * middle is something that doesn't repeat within one second on a single
+ * host. I fork a new process for each delivery, so I just use the
+ * process ID. If you're delivering several messages from one process,
+ * use starttime.pid_count.host, where starttime is the time that your
+ * process started, and count is the number of messages you've
+ * delivered."
+ */
+#define STR vstring_str
+
+ set_eugid(usr_attr.uid, usr_attr.gid);
+ vstring_sprintf(buf, "%ld.%d_%d.%s", (long) var_starttime,
+ var_pid, count++, get_hostname());
+ tmpfile = concatenate(tmpdir, STR(buf), (char *) 0);
+ newfile = concatenate(newdir, STR(buf), (char *) 0);
+ if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0
+ && (errno != ENOENT
+ || make_dirs(tmpdir, 0700) < 0
+ || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) {
+ vstring_sprintf(why, "create %s: %m", tmpfile);
+ } else {
+ if (mail_copy(COPY_ATTR(state.msg_attr), dst, copy_flags, "\n", why) == 0) {
+ if (sane_link(tmpfile, newfile) < 0
+ && (errno != ENOENT
+ || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0
+ || sane_link(tmpfile, newfile) < 0)) {
+ vstring_sprintf(why, "link to %s: %m", newfile);
+ } else {
+ status = 0;
+ }
+ }
+ if (unlink(tmpfile) < 0)
+ msg_warn("remove %s: %m", tmpfile);
+ }
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * The maildir location is controlled by the mail administrator. If
+ * delivery fails, try again later. We would just bounce when the maildir
+ * location possibly under user control.
+ */
+ if (status)
+ status = (errno == EDQUOT || errno == EFBIG ?
+ bounce_append : defer_append)
+ (BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "maildir delivery failed: %s", vstring_str(why));
+
+ else
+ status = sent(SENT_ATTR(state.msg_attr), "maildir");
+ vstring_free(buf);
+ vstring_free(why);
+ myfree(newdir);
+ myfree(tmpdir);
+ myfree(curdir);
+ myfree(tmpfile);
+ myfree(newfile);
+ return (status);
+}
--- /dev/null
+/*++
+/* NAME
+/* recipient 3
+/* SUMMARY
+/* deliver to one local recipient
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_recipient(state, usr_attr)
+/* LOCAL_STATE state;
+/* USER_ATTR *usr_attr;
+/* DESCRIPTION
+/* deliver_recipient() delivers a message to a local recipient.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, sender, and more.
+/* .IP usr_attr
+/* Attributes describing user rights and mailbox location.
+/* DIAGNOSTICS
+/* deliver_recipient() returns non-zero when delivery should be
+/* tried again.
+/* SEE ALSO
+/* mailbox(3) delivery to UNIX-style mailbox
+/* maildir(3) delivery to qmail-style maildir
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <bounce.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_recipient - deliver one local recipient */
+
+int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ char *myname = "deliver_recipient";
+ int rcpt_stat;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Set up the recipient-specific attributes. The recipient's lookup
+ * handle is the full address.
+ */
+ if (state.msg_attr.delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.recipient;
+ state.msg_attr.user = mystrdup(state.msg_attr.recipient);
+ lowercase(state.msg_attr.user);
+
+ /*
+ * Deliver
+ */
+ if (msg_verbose)
+ deliver_attr_dump(&state.msg_attr);
+
+ if (deliver_mailbox(state, usr_attr, &rcpt_stat) == 0)
+ rcpt_stat = deliver_unknown(state);
+
+ /*
+ * Cleanup.
+ */
+ myfree(state.msg_attr.user);
+
+ return (rcpt_stat);
+}
--- /dev/null
+/*++
+/* NAME
+/* unknown 3
+/* SUMMARY
+/* delivery of unknown recipients
+/* SYNOPSIS
+/* #include "virtual.h"
+/*
+/* int deliver_unknown(state)
+/* LOCAL_STATE state;
+/* DESCRIPTION
+/* deliver_unknown() delivers a message for unknown recipients.
+/* .PP
+/* Arguments:
+/* .IP state
+/* Message delivery attributes (sender, recipient etc.).
+/* .IP usr_attr
+/* Attributes describing user rights and mailbox location.
+/* DIAGNOSTICS
+/* The result status is non-zero when delivery should be tried again.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include <bounce.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+/* deliver_unknown - delivery for unknown recipients */
+
+int deliver_unknown(LOCAL_STATE state)
+{
+ char *myname = "deliver_unknown";
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ return (bounce_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "unknown user: \"%s\"", state.msg_attr.user));
+
+}
--- /dev/null
+/*++
+/* NAME
+/* virtual 8
+/* SUMMARY
+/* Postfix virtual domain mail delivery agent
+/* SYNOPSIS
+/* \fBvirtual\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBvirtual\fR delivery agent is designed for virtual mail
+/* hosting services. Originally based on the Postfix local delivery
+/* agent, this agent looks up recipients with map lookups of their
+/* full recipient address, instead of using hard-coded unix password
+/* file lookups of the address local part only.
+/*
+/* This delivery agent only delivers mail. Other features such as
+/* mail forwarding, out-of-office notifications, etc., must be
+/* configured via virtual maps or via similar lookup mechanisms.
+/* MAILBOX LOCATION
+/* .ad
+/* .fi
+/* The mailbox location is controlled by the \fBvirtual_mailbox_base\fR
+/* and \fBvirtual_mailbox_maps\fR configuration parameters (see below).
+/* The \fBvirtual_mailbox_maps\fR table is indexed by the full recipient
+/* address.
+/*
+/* The mailbox pathname is constructed as follows:
+/*
+/* .ti +2
+/* \fB$virtual_mailbox_base/$virtual_mailbox_maps(\fIrecipient\fB)\fR
+/*
+/* where \fIrecipient\fR is the full recipient address.
+/* UNIX MAILBOX FORMAT
+/* .ad
+/* .fi
+/* When the mailbox location does not end in \fB/\fR, the message
+/* is delivered in UNIX mailbox format. This format stores multiple
+/* messages in one textfile.
+/*
+/* The \fBvirtual\fR delivery agent prepends a "\fBFrom \fIsender
+/* time_stamp\fR" envelope header to each message, prepends a
+/* \fBDelivered-To:\fR message header with the envelope recipient
+/* address, prepends a \fBReturn-Path:\fR message header with the
+/* envelope sender address, prepends a \fB>\fR character to lines
+/* beginning with "\fBFrom \fR", and appends an empty line.
+/*
+/* The mailbox is locked for exclusive access while delivery is in
+/* progress. In case of problems, an attempt is made to truncate the
+/* mailbox to its original length.
+/* QMAIL MAILDIR FORMAT
+/* .ad
+/* .fi
+/* When the mailbox location ends in \fB/\fR, the message is delivered
+/* in qmail \fBmaildir\fR format. This format stores one message per file.
+/*
+/* The \fBvirtual\fR delivery agent daemon prepends a \fBDelivered-To:\fR
+/* message header with the envelope recipient address and prepends a
+/* \fBReturn-Path:\fR message header with the envelope sender address.
+/*
+/* By definition, \fBmaildir\fR format does not require file locking
+/* during mail delivery or retrieval.
+/* MAILBOX OWNERSHIP
+/* .ad
+/* .fi
+/* Mailbox ownership is controlled by the \fBvirtual_uid_maps\fR
+/* and \fBvirtual_gid_maps\fR lookup tables, which are indexed
+/* with the full recipient address. Each table provides
+/* a string with the numerical user and group ID, respectively.
+/*
+/* The \fBvirtual_minimum_uid\fR parameter imposes a lower bound on
+/* numerical user ID values that may be specified in any
+/* \fBvirtual_owner_maps\fR or \fBvirtual_uid_maps\fR.
+/* SECURITY
+/* The virtual delivery agent is not security sensitive, provided
+/* that the lookup tables with recipient information are adequately
+/* protected. This program is not designed to run chrooted.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* DIAGNOSTICS
+/* Mail bounces when the recipient has no mailbox or when the
+/* recipient is over disk quota. In all other cases, mail for
+/* an existing recipient is deferred and a warning is logged.
+/*
+/* Problems and transactions are logged to \fBsyslogd\fR(8).
+/* Corrupted message files are marked so that the queue
+/* manager can move them to the \fBcorrupt\fR queue afterwards.
+/*
+/* Depending on the setting of the \fBnotify_classes\fR parameter,
+/* the postmaster is notified of bounces and of other trouble.
+/* BUGS
+/* This delivery agent silently ignores address extensions.
+/*
+/* Postfix should have lookup tables that can return multiple result
+/* attributes. In order to avoid the inconvenience of maintaining
+/* three tables, use an LDAP or MYSQL database.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program. See the Postfix \fBmain.cf\fR file for syntax details
+/* and for default values. Use the \fBpostfix reload\fR command after
+/* a configuration change.
+/* .SH Mailbox delivery
+/* .ad
+/* .fi
+/* .IP \fBvirtual_mailbox_base\fR
+/* Specifies a path that is prepended to all mailbox or maildir paths.
+/* This is a safety measure to ensure that an out of control map in
+/* \fBvirtual_mailbox_maps\fR doesn't litter the filesystem with mailboxes.
+/* While it could be set to "/", this setting isn't recommended.
+/* .IP \fBvirtual_mailbox_maps\fR
+/* Recipients are looked up in these maps to determine the path to
+/* their mailbox or maildir. If the returned path ends in a slash
+/* ("/"), maildir-style delivery is carried out, otherwise the
+/* path is assumed to specify a UNIX-style mailbox file.
+/*
+/* Note that \fBvirtual_mailbox_base\fR is unconditionally prepended
+/* to this path.
+/* .IP \fBvirtual_minimum_uid\fR
+/* Specifies a minimum uid that will be accepted as a return from
+/* a \fBvirtual_owner_maps\fR or \fBvirtual_uid_maps\fR lookup.
+/* Returned values less than this will be rejected, and the message
+/* will be deferred.
+/* .IP \fBvirtual_uid_maps\fR
+/* Recipients are looked up in these maps to determine the user ID to be
+/* used when writing to the target mailbox.
+/* .IP \fBvirtual_gid_maps\fR
+/* Recipients are looked up in these maps to determine the group ID to be
+/* used when writing to the target mailbox.
+/* .SH "Locking controls"
+/* .ad
+/* .fi
+/* .IP \fBvirtual_mailbox_lock\fR
+/* How to lock UNIX-style mailboxes: one or more of \fBflock\fR,
+/* \fBfcntl\fR or \fBdotlock\fR. The \fBdotlock\fR method requires
+/* that the recipient UID or GID has write access to the parent
+/* directory of the mailbox file.
+/*
+/* This setting is ignored with \fBmaildir\fR style delivery,
+/* because such deliveries are safe without explicit locks.
+/*
+/* Use the command \fBpostconf -m\fR to find out what locking methods
+/* are available on your system.
+/* .IP \fBdeliver_lock_attempts\fR
+/* Limit the number of attempts to acquire an exclusive lock
+/* on a UNIX-style mailbox file.
+/* .IP \fBdeliver_lock_delay\fR
+/* Time (default: seconds) between successive attempts to acquire
+/* an exclusive lock on a UNIX-style mailbox file. The actual delay
+/* is slightly randomized.
+/* .IP \fBstale_lock_time\fR
+/* Limit the time after which a stale lockfile is removed (applicable
+/* to UNIX-style mailboxes only).
+/* .SH "Resource controls"
+/* .ad
+/* .fi
+/* .IP \fBvirtual_destination_concurrency_limit\fR
+/* Limit the number of parallel deliveries to the same domain
+/* via the \fBvirtual\fR delivery agent.
+/* The default limit is taken from the
+/* \fBdefault_destination_concurrency_limit\fR parameter.
+/* The limit is enforced by the Postfix queue manager.
+/* .IP \fBvirtual_destination_recipient_limit\fR
+/* Limit the number of recipients per message delivery
+/* via the \fBvirtual\fR delivery agent.
+/* The default limit is taken from the
+/* \fBdefault_destination_recipient_limit\fR parameter.
+/* The limit is enforced by the Postfix queue manager.
+/* .IP \fBvirtual_mailbox_limit\fR
+/* The maximal size in bytes of a mailbox or maildir file.
+/* HISTORY
+/* .ad
+/* .fi
+/* This agent was originally based on the Postfix local delivery
+/* agent. Modifications mainly consisted of removing code that either
+/* was not applicable or that was not safe in this context: aliases,
+/* ~user/.forward files, delivery to "|command" or to /file/name.
+/*
+/* The \fBDelivered-To:\fR header appears in the \fBqmail\fR system
+/* by Daniel Bernstein.
+/*
+/* The \fBmaildir\fR structure appears in the \fBqmail\fR system
+/* by Daniel Bernstein.
+/* SEE ALSO
+/* bounce(8) non-delivery status reports
+/* syslogd(8) system logging
+/* qmgr(8) queue manager
+/* 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
+/*
+/* Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney 2060, NSW, Australia
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <iostuff.h>
+#include <set_eugid.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <recipient_list.h>
+#include <deliver_request.h>
+#include <deliver_completed.h>
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "virtual.h"
+
+ /*
+ * Tunable parameters.
+ */
+char *var_virt_mailbox_maps;
+char *var_virt_uid_maps;
+char *var_virt_gid_maps;
+int var_virt_minimum_uid;
+char *var_virt_mailbox_base;
+char *var_virt_mailbox_lock;
+int var_virt_mailbox_limit;
+
+ /*
+ * Mappings.
+ */
+MAPS *virtual_mailbox_maps;
+MAPS *virtual_uid_maps;
+MAPS *virtual_gid_maps;
+
+ /*
+ * Bit masks.
+ */
+int virtual_mbox_lock_mask;
+
+/* local_deliver - deliver message with extreme prejudice */
+
+static int local_deliver(DELIVER_REQUEST *rqst, char *service)
+{
+ char *myname = "local_deliver";
+ RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len;
+ RECIPIENT *rcpt;
+ int rcpt_stat;
+ int msg_stat;
+ LOCAL_STATE state;
+ USER_ATTR usr_attr;
+
+ if (msg_verbose)
+ msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender);
+
+ /*
+ * Initialize the delivery attributes that are not recipient specific.
+ */
+ state.level = 0;
+ deliver_attr_init(&state.msg_attr);
+ state.msg_attr.queue_name = rqst->queue_name;
+ state.msg_attr.queue_id = rqst->queue_id;
+ state.msg_attr.fp = rqst->fp;
+ state.msg_attr.offset = rqst->data_offset;
+ state.msg_attr.sender = rqst->sender;
+ state.msg_attr.relay = service;
+ state.msg_attr.arrival_time = rqst->arrival_time;
+ RESET_USER_ATTR(usr_attr, state.level);
+ state.request = rqst;
+
+ /*
+ * Iterate over each recipient named in the delivery request. When the
+ * mail delivery status for a given recipient is definite (i.e. bounced
+ * or delivered), update the message queue file and cross off the
+ * recipient. Update the per-message delivery status.
+ */
+ for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) {
+ state.msg_attr.recipient = rcpt->address;
+ rcpt_stat = deliver_recipient(state, usr_attr);
+ if (rcpt_stat == 0)
+ deliver_completed(state.msg_attr.fp, rcpt->offset);
+ msg_stat |= rcpt_stat;
+ }
+
+ return (msg_stat);
+}
+
+/* local_service - perform service for client */
+
+static void local_service(VSTREAM *stream, char *service, char **argv)
+{
+ DELIVER_REQUEST *request;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * that is dedicated to local mail delivery service. What we see below is
+ * a little protocol to (1) tell the client that we are ready, (2) read a
+ * delivery request from the client, and (3) report the completion status
+ * of that request.
+ */
+ if ((request = deliver_request_read(stream)) != 0) {
+ status = local_deliver(request, service);
+ deliver_request_done(stream, request, status);
+ }
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ if (dict_changed()) {
+ msg_info("table has changed -- exiting");
+ exit(0);
+ }
+}
+
+/* post_init - post-jail initialization */
+
+static void post_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Drop privileges most of the time.
+ */
+ set_eugid(var_owner_uid, var_owner_gid);
+
+ virtual_mailbox_maps =
+ maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps,
+ DICT_FLAG_LOCK);
+
+ virtual_uid_maps =
+ maps_create(VAR_VIRT_UID_MAPS, var_virt_uid_maps, DICT_FLAG_LOCK);
+
+ virtual_gid_maps =
+ maps_create(VAR_VIRT_GID_MAPS, var_virt_gid_maps, DICT_FLAG_LOCK);
+
+ virtual_mbox_lock_mask = mbox_lock_mask(var_virt_mailbox_lock);
+}
+
+/* pre_init - pre-jail initialization */
+
+static void pre_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Reset the file size limit from the message size limit to the mailbox
+ * size limit. XXX This still isn't accurate because the file size limit
+ * also affects delivery to command.
+ *
+ * We can't have mailbox size limit smaller than the message size limit,
+ * because that prohibits the delivery agent from updating the queue
+ * file.
+ */
+ if (var_virt_mailbox_limit < var_message_limit)
+ msg_fatal("main.cf configuration error: %s is smaller than %s",
+ VAR_VIRT_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT);
+ set_file_limit(var_virt_mailbox_limit);
+}
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static CONFIG_INT_TABLE int_table[] = {
+ VAR_VIRT_MINUID, DEF_VIRT_MINUID, &var_virt_minimum_uid, 1, 0,
+ VAR_VIRT_MAILBOX_LIMIT, DEF_VIRT_MAILBOX_LIMIT, &var_virt_mailbox_limit, 1, 0,
+ 0,
+ };
+ static CONFIG_STR_TABLE str_table[] = {
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
+ VAR_VIRT_UID_MAPS, DEF_VIRT_UID_MAPS, &var_virt_uid_maps, 0, 0,
+ VAR_VIRT_GID_MAPS, DEF_VIRT_GID_MAPS, &var_virt_gid_maps, 0, 0,
+ VAR_VIRT_MAILBOX_BASE, DEF_VIRT_MAILBOX_BASE, &var_virt_mailbox_base, 0, 0,
+ VAR_VIRT_MAILBOX_LOCK, DEF_VIRT_MAILBOX_LOCK, &var_virt_mailbox_lock, 1, 0,
+ 0,
+ };
+
+ single_server_main(argc, argv, local_service,
+ MAIL_SERVER_INT_TABLE, int_table,
+ MAIL_SERVER_STR_TABLE, str_table,
+ MAIL_SERVER_PRE_INIT, pre_init,
+ MAIL_SERVER_POST_INIT, post_init,
+ MAIL_SERVER_PRE_ACCEPT, pre_accept,
+ 0);
+}
--- /dev/null
+/*++
+/* NAME
+/* virtual 3h
+/* SUMMARY
+/* virtual mail delivery
+/* SYNOPSIS
+/* #include "virtual.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <deliver_request.h>
+#include <maps.h>
+#include <mbox_conf.h>
+
+ /*
+ * Mappings.
+ */
+extern MAPS *virtual_mailbox_maps;
+extern MAPS *virtual_uid_maps;
+extern MAPS *virtual_gid_maps;
+
+ /*
+ * User attributes: these control the privileges for delivery to external
+ * commands, external files, or mailboxes, and the initial environment of
+ * external commands.
+ */
+typedef struct USER_ATTR {
+ uid_t uid; /* file/command access */
+ gid_t gid; /* file/command access */
+ char *mailbox; /* mailbox file or directory */
+} USER_ATTR;
+
+ /*
+ * Critical macros. Not for obscurity, but to ensure consistency.
+ */
+#define RESET_USER_ATTR(usr_attr, level) { \
+ usr_attr.uid = 0; usr_attr.gid = 0; usr_attr.mailbox = 0; \
+ if (msg_verbose) \
+ msg_info("%s[%d]: reset user_attr", myname, level); \
+ }
+
+ /*
+ * The delivery attributes are inherited from files, from aliases, and from
+ * whatnot. Some of the information is changed on the fly. DELIVER_ATTR
+ * structres are therefore passed by value, so there is no need to undo
+ * changes.
+ */
+typedef struct DELIVER_ATTR {
+ int level; /* recursion level */
+ VSTREAM *fp; /* open queue file */
+ char *queue_name; /* mail queue id */
+ char *queue_id; /* mail queue id */
+ long offset; /* data offset */
+ char *sender; /* taken from envelope */
+ char *recipient; /* taken from resolver */
+ char *user; /* recipient lookup handle */
+ char *delivered; /* for loop detection */
+ char *relay; /* relay host */
+ long arrival_time; /* arrival time */
+} DELIVER_ATTR;
+
+extern void deliver_attr_init(DELIVER_ATTR *);
+extern void deliver_attr_dump(DELIVER_ATTR *);
+
+#define FEATURE_NODELIVERED (1<<0) /* no delivered-to */
+
+ /*
+ * Rather than schlepping around dozens of arguments, here is one that has
+ * all. Well, almost. The user attributes are just a bit too sensitive, so
+ * they are passed around separately.
+ */
+typedef struct LOCAL_STATE {
+ int level; /* nesting level, for logging */
+ DELIVER_ATTR msg_attr; /* message attributes */
+ DELIVER_REQUEST *request; /* as from queue manager */
+} LOCAL_STATE;
+
+ /*
+ * Bundle up some often-user attributes.
+ */
+#define BOUNCE_ATTR(attr) attr.queue_id, attr.recipient, attr.relay, \
+ attr.arrival_time
+#define SENT_ATTR(attr) attr.queue_id, attr.recipient, attr.relay, \
+ attr.arrival_time
+#define COPY_ATTR(attr) attr.sender, attr.delivered, attr.fp
+
+#define MSG_LOG_STATE(m, p) \
+ msg_info("%s[%d]: recip %s deliver %s", m, \
+ p.level, \
+ p.msg_attr.recipient ? p.msg_attr.recipient : "", \
+ p.msg_attr.delivered ? p.msg_attr.delivered : "")
+
+ /*
+ * "inner" nodes of the delivery graph.
+ */
+extern int deliver_recipient(LOCAL_STATE, USER_ATTR);
+
+ /*
+ * "leaf" nodes of the delivery graph.
+ */
+extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *);
+extern int deliver_file(LOCAL_STATE, USER_ATTR, char *);
+extern int deliver_maildir(LOCAL_STATE, USER_ATTR);
+extern int deliver_unknown(LOCAL_STATE);
+
+ /*
+ * Mailbox lock protocol.
+ */
+extern int virtual_mbox_lock_mask;
+
+/* 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
+/*--*/