]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
snapshot-20010128
authorWietse Venema <wietse@porcupine.org>
Sun, 28 Jan 2001 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:27:06 +0000 (06:27 +0000)
52 files changed:
postfix/0README
postfix/COMPATIBILITY
postfix/HISTORY
postfix/LDAP_README
postfix/LMTP_README
postfix/Makefile.in
postfix/RELEASE_NOTES
postfix/VIRTUAL_README [new file with mode: 0644]
postfix/conf/master.cf
postfix/conf/sample-ldap.cf
postfix/html/Makefile.in
postfix/html/delivering.html
postfix/html/virtual.8.html [new file with mode: 0644]
postfix/man/Makefile.in
postfix/man/man8/virtual.8 [new file with mode: 0644]
postfix/src/dns/dns_lookup.c
postfix/src/flush/flush.c
postfix/src/global/Makefile.in
postfix/src/global/mail_params.c
postfix/src/global/mail_params.h
postfix/src/global/mail_queue.c
postfix/src/global/mail_version.h
postfix/src/global/peer_name.c
postfix/src/local/local.c
postfix/src/nqmgr/qmgr.h
postfix/src/nqmgr/qmgr_deliver.c
postfix/src/nqmgr/qmgr_entry.c
postfix/src/nqmgr/qmgr_job.c
postfix/src/nqmgr/qmgr_message.c
postfix/src/nqmgr/qmgr_queue.c
postfix/src/nqmgr/qmgr_transport.c
postfix/src/postsuper/postsuper.c
postfix/src/qmgr/qmgr_bounce.c
postfix/src/qmgr/qmgr_message.c
postfix/src/smtpd/Makefile.in
postfix/src/smtpd/smtpd.c
postfix/src/smtpd/smtpd_check.c
postfix/src/smtpd/smtpd_peer.c
postfix/src/util/dict_ldap.c
postfix/src/util/get_hostname.c
postfix/src/util/valid_hostname.c
postfix/src/util/valid_hostname.h
postfix/src/virtual/.indent.pro [new symlink]
postfix/src/virtual/.printfck [new file with mode: 0644]
postfix/src/virtual/Makefile.in [new file with mode: 0644]
postfix/src/virtual/deliver_attr.c [new file with mode: 0644]
postfix/src/virtual/mailbox.c [new file with mode: 0644]
postfix/src/virtual/maildir.c [new file with mode: 0644]
postfix/src/virtual/recipient.c [new file with mode: 0644]
postfix/src/virtual/unknown.c [new file with mode: 0644]
postfix/src/virtual/virtual.c [new file with mode: 0644]
postfix/src/virtual/virtual.h [new file with mode: 0644]

index c399b25190a5a9307e8c3cc077b8cf001bdb8e17..01ac09a24a4bed2f4ad36994222cfbfcc3bc3022 100644 (file)
@@ -40,7 +40,6 @@ On-line resources devoted to the Postfix mail system
 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)
 
@@ -146,6 +145,7 @@ Postfix daemons:
     src/smtp/          SMTP client
     src/smtpd/         SMTP server
     src/trivial-rewrite/ Address rewriting and resolving
+    src/virtual/       virtual mailbox-only delivery agent
 
 Test programs:
 
index 4c161b9208df7c48a7cc57908519ab2fc9ec3b4b..bb754a363b156510168de80914fa9d55fde07fb2 100644 (file)
@@ -7,20 +7,21 @@
 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:)
@@ -30,6 +31,7 @@ mailq         yes
 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
@@ -38,8 +40,10 @@ pipeline option      yes (server and client)
 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
@@ -49,7 +53,7 @@ smarthost     yes
 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
index 25b5d9c64206d2be9e822c6d9661c669baec026e..4b849abcd7d333bb5469228cdee3b3db8ae13e52 100644 (file)
@@ -4733,7 +4733,6 @@ Apologies for any names omitted.
 
        Feature: SASL support for the LMTP client. Recent CYRUS
        software requires this for Postfix over TCP sockets.
-       This was just a cloning operation.
 
 20010120
 
@@ -4745,6 +4744,10 @@ Apologies for any names omitted.
 
 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.
 
@@ -4756,13 +4759,33 @@ Apologies for any names omitted.
        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.
index 07660e03108f411cab3916c46985c7003c15db19..0c862c089d7d0001677d0324a239a53e48cc3229 100644 (file)
@@ -86,11 +86,12 @@ parameter below, "server_host", would be defined in main.cf as
        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)
index 42954076f089b6dad68e9fdbaa79ca1bc3a9aad6..60c7d07d3e04e950152524254aee288e021ddc40 100644 (file)
@@ -1,3 +1,5 @@
+[This file still needs to be updated - some information is obsolete]
+
 Postfix LMTP support
 ====================
 
index c01787d746ba421f288b732873a01c37c7b795bd..7046d405066db4c8146510d4f63938b65a8c9da3 100644 (file)
@@ -6,7 +6,7 @@ DIRS    = src/util src/global src/dns src/master src/postfix src/smtpstone \
        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
 
index af3e6c7bfc4a93b798d039958e3952f0d322bef9..15df247200ba925fee17f937e236f5357de10f70 100644 (file)
@@ -1,5 +1,44 @@
-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
 ====================================
diff --git a/postfix/VIRTUAL_README b/postfix/VIRTUAL_README
new file mode 100644 (file)
index 0000000..fae2097
--- /dev/null
@@ -0,0 +1,259 @@
+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.
index 15deb0142e1bd5e4b4b527b9d5d1d82d829bea13..e00d7c6834977b459a9a35e0d79b10223a68e326 100644 (file)
@@ -80,6 +80,7 @@ smtp    unix  -       -       n       -       -       smtp
 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}
index 00620b31121a5250a94979632a04257206086a4a..4f8a504d6078d6fff92c5e6b1d289df67a1e18b6 100644 (file)
@@ -67,3 +67,8 @@
 # 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 =
index 8a342e1fc09bf57990f7a85554f42971aaa09edf..6d1b3270f7799a4ecf861cd75e5437420b10eedf 100644 (file)
@@ -5,7 +5,7 @@ SHELL   = /bin/sh
 DAEMONS        =  bounce.8.html cleanup.8.html defer.8.html error.8.html local.8.html \
        lmtp.8.html master.8.html pickup.8.html pipe.8.html qmgr.8.html \
        showq.8.html smtp.8.html smtpd.8.html trivial-rewrite.8.html \
-       nqmgr.8.html spawn.8.html flush.8.html
+       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 \
@@ -78,6 +78,9 @@ smtp.8.html: ../src/smtp/smtp.c
 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 >$@
 
index a39b603afaf05c38ab7e3e8bf9ed0b862f3e2f3b..ed2a9e533d40e73a4a2bf2e002e393e0b6db3e42 100644 (file)
@@ -108,6 +108,20 @@ such as the popular <a href="faq.html#procmail">procmail</a> program.
 
 <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
@@ -116,6 +130,15 @@ client processes running in parallel.
 
 <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).
diff --git a/postfix/html/virtual.8.html b/postfix/html/virtual.8.html
new file mode 100644 (file)
index 0000000..8fd6bbb
--- /dev/null
@@ -0,0 +1,332 @@
+<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 &gt; 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>
index 8e258047de1645f1487b15e8d2585d71405dbd0c..489162160d6d1f4a42ce19b7a3c4fc258044ba38 100644 (file)
@@ -5,7 +5,7 @@ SHELL   = /bin/sh
 DAEMONS        = man8/bounce.8 man8/defer.8 man8/cleanup.8 man8/error.8 man8/local.8 \
        man8/lmtp.8 man8/master.8 man8/pickup.8 man8/pipe.8 man8/qmgr.8 \
        man8/showq.8 man8/smtp.8 man8/smtpd.8 man8/trivial-rewrite.8 \
-       man8/nqmgr.8 man8/spawn.8 man8/flush.8
+       man8/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 \
@@ -77,6 +77,9 @@ man8/smtp.8: ../src/smtp/smtp.c
 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 $? >$@
 
diff --git a/postfix/man/man8/virtual.8 b/postfix/man/man8/virtual.8
new file mode 100644 (file)
index 0000000..6e007bc
--- /dev/null
@@ -0,0 +1,233 @@
+.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
index fedd6a8700093ca5ab9590fedc537f68b139b06f..a25334cea22177fa165b38e7f4f04bf209a27e7b 100644 (file)
@@ -241,6 +241,50 @@ static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed)
     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,
@@ -270,7 +314,7 @@ 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;
@@ -278,7 +322,7 @@ static DNS_RR *dns_get_rr(DNS_REPLY *reply, unsigned char *pos,
        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;
@@ -315,7 +359,7 @@ static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
        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);
 }
@@ -369,10 +413,6 @@ static int dns_get_answer(DNS_REPLY *reply, int type,
        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;
 
        /*
@@ -382,6 +422,10 @@ static int dns_get_answer(DNS_REPLY *reply, int type,
            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);
@@ -434,7 +478,7 @@ int     dns_lookup(const char *name, unsigned type, unsigned flags,
     /*
      * 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);
index 0ec9840d972d3964f32a436498564e6e6bbdf9d4..d35cc5813f91040a9e2bea6595824675b520b607 100644 (file)
@@ -466,14 +466,14 @@ static void flush_service(VSTREAM *client_stream, char *unused_service,
            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)
index a9a5fffd9fb214f0df8bc48761c2c01068c6f8d5..99b16fc42cf20c56fc3b34760dff2576e2fa6cbe 100644 (file)
@@ -346,6 +346,7 @@ deliver_flock.o: ../../include/sys_defs.h
 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
@@ -387,6 +388,7 @@ dot_lockfile.o: ../../include/vstring.h
 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
@@ -767,6 +769,8 @@ mkmap_open.o: ../../include/dict.h
 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
index 5d1c60fd8011b08f75719ce854aba54236ee1af2..d300ece4de008769aa7912812062d02bf62e1d8d 100644 (file)
@@ -340,8 +340,10 @@ void    mail_params_init()
      * 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.
index 9980059490fb7ba1a32f7e5a0ba0e834d1695de7..4b095a774cb7940d58c8bbb834b4728b5b7c0103 100644 (file)
@@ -379,6 +379,14 @@ extern char *var_forward_path;
 #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.
   */
@@ -1178,6 +1186,41 @@ extern char *var_import_environ;
 #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
index bb83f566ddb6c81ee1ae717ee32e8299ba35e0b2..5fc18640ec38c02b2ebfc3529f58c25dc68cae84 100644 (file)
@@ -311,7 +311,7 @@ int     mail_queue_id_ok(const char *queue_id)
     /*
      * 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 */
index d6e594e599f23144e94ef01e0af0942e9eb61780..2ae52c23436bd651378e63b95b0ed38bb09ee5df 100644 (file)
@@ -15,7 +15,7 @@
   * 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
index 63d408c2e54b57dbd3d0f2ba3aba73064d13d586..5e3affa5a843b1fb3cfdba93edd8a9f2982caf51 100644 (file)
@@ -79,7 +79,7 @@ PEER_NAME *peer_name(int sock)
            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);
index 3a1cb5fc856905835b18cc51b2257d14e4134a6e..d31e084bfae87e47844f70f0ef7208cdd64134af 100644 (file)
@@ -456,6 +456,7 @@ char   *var_deliver_hdr;
 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;
@@ -621,6 +622,26 @@ static void post_init(char *unused_name, char **unused_argv)
     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)
@@ -631,6 +652,7 @@ 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[] = {
@@ -671,6 +693,7 @@ int     main(int argc, char **argv)
                       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);
index 2da9dea0b403527621ce6a71a7bfad61701955b3..e32c64bf4f15973b29d19dd436318615263a581b 100644 (file)
@@ -60,6 +60,15 @@ typedef struct QMGR_SCAN QMGR_SCAN;
     object->peers.next = object->peers.prev = 0; \
 }
 
+#define QMGR_LIST_LINK(head, pred, object, succ, peers) { \
+    object->peers.prev = pred; \
+    object->peers.next = succ; \
+    if (pred) pred->peers.next = object; \
+    else head.next = object; \
+    if (succ) succ->peers.prev = object; \
+    else head.prev = object; \
+}
+
 #define QMGR_LIST_PREPEND(head, object, peers) { \
     object->peers.next = head.next; \
     object->peers.prev = 0; \
@@ -121,8 +130,8 @@ struct QMGR_TRANSPORT {
     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) */
@@ -134,13 +143,16 @@ struct QMGR_TRANSPORT {
     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 */
 };
@@ -178,6 +190,7 @@ struct QMGR_QUEUE {
     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 */
@@ -298,9 +311,13 @@ struct QMGR_JOB {
     QMGR_TRANSPORT *transport;         /* transport this job belongs to */
     QMGR_JOB_LIST message_peers;       /* per message neighbor linkage */
     QMGR_JOB_LIST transport_peers;     /* per transport neighbor linkage */
-    QMGR_JOB_LIST 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 */
index 3c479746f0f3bccb3e47f275190ab0a7df53a9af..b064210d60fd5f3eacd193029aca70dd0508537c 100644 (file)
@@ -2,7 +2,7 @@
 /* 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"
 /*
index 079ae7a60f9393a45a5d1cd8816e37813aa1f6fd..e6b84f577dba4b3784ba1542e2eb0098c5180a09 100644 (file)
@@ -140,6 +140,7 @@ void    qmgr_entry_done(QMGR_ENTRY *entry, int which)
     QMGR_PEER *peer = entry->peer;
     QMGR_JOB *sponsor,
            *job = peer->job;
+    QMGR_TRANSPORT *transport = job->transport;
 
     /*
      * Take this entry off the in-core queue.
@@ -171,7 +172,7 @@ void    qmgr_entry_done(QMGR_ENTRY *entry, int which)
     /*
      * 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) {
@@ -184,6 +185,26 @@ void    qmgr_entry_done(QMGR_ENTRY *entry, int which)
        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.
index 3be930997c9efca59cfb3c6297002f24a63ee105..7970fe8eec326324e4b3762877bd46b87416bab9 100644 (file)
@@ -83,10 +83,9 @@ static void qmgr_job_pop(QMGR_JOB *);
 #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 */
 
@@ -100,9 +99,14 @@ static QMGR_JOB *qmgr_job_create(QMGR_MESSAGE *message, QMGR_TRANSPORT *transpor
     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;
@@ -112,7 +116,7 @@ static QMGR_JOB *qmgr_job_create(QMGR_MESSAGE *message, QMGR_TRANSPORT *transpor
     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)
 {
@@ -120,24 +124,55 @@ 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;
@@ -146,36 +181,18 @@ static void qmgr_job_link(QMGR_JOB *job)
     }
 
     /*
-     * 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
@@ -221,28 +238,32 @@ QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
     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)
 {
@@ -254,10 +275,11 @@ 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;
@@ -274,7 +296,8 @@ void    qmgr_job_move_limits(QMGR_JOB *job)
     /*
      * 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;
@@ -288,43 +311,93 @@ void    qmgr_job_move_limits(QMGR_JOB *job)
     }
 }
 
-/* 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)
@@ -343,7 +416,7 @@ 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);
@@ -356,18 +429,11 @@ void    qmgr_job_free(QMGR_JOB *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);
@@ -376,38 +442,32 @@ void    qmgr_job_free(QMGR_JOB *job)
 
 /* 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 */
@@ -429,9 +489,11 @@ static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current)
      * 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);
 
     /*
@@ -447,6 +509,17 @@ static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current)
      * 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.
      * 
@@ -454,8 +527,8 @@ static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current)
      * 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;
@@ -471,7 +544,7 @@ static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current)
            /*
             * 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;
        }
     }
@@ -480,6 +553,7 @@ static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current)
      * Cache the result for later use.
      */
     transport->candidate_cache = best_job;
+    transport->candidate_cache_current = current;
     transport->candidate_cache_time = now;
 
     return (best_job);
@@ -491,7 +565,9 @@ static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current)
 {
     char   *myname = "qmgr_job_preempt";
     QMGR_TRANSPORT *transport = current->transport;
-    QMGR_JOB *job;
+    QMGR_JOB *job,
+           *prev;
+    int     expected_slots;
     int     rcpt_slots;
 
     /*
@@ -520,8 +596,10 @@ static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current)
      */
     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
@@ -529,22 +607,55 @@ static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current)
      * 
      * 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.
@@ -558,29 +669,29 @@ static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current)
        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.
@@ -590,32 +701,50 @@ static void qmgr_job_pop(QMGR_JOB *job)
      * 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 */
@@ -646,16 +775,14 @@ static QMGR_PEER *qmgr_job_peer_select(QMGR_JOB *job)
 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);
 
     /*
@@ -665,36 +792,52 @@ QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *transport)
      * 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
@@ -702,47 +845,49 @@ QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *transport)
             * 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);
 }
index 722f44a14661f70af73be6eb85529483a6e54736..44e6b9735c9e88eb32ddc380b6a6594a528464db 100644 (file)
@@ -303,8 +303,6 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
     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;
@@ -505,14 +503,14 @@ static int qmgr_message_sort_compare(const void *p1, const void *p2)
        /*
         * 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);
     }
 
@@ -527,7 +525,7 @@ static int qmgr_message_sort_compare(const void *p1, const void *p2)
     /*
      * Compare recipient address.
      */
-    return (strcasecmp(rcpt1->address, rcpt2->address));
+    return (strcmp(rcpt1->address, rcpt2->address));
 }
 
 /* qmgr_message_sort - sort message recipient addresses by domain */
@@ -565,7 +563,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
     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); }
@@ -583,7 +581,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
         */
        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;
@@ -699,8 +697,8 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
             * 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);
@@ -718,7 +716,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
            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,
@@ -805,25 +803,61 @@ static void qmgr_message_assign(QMGR_MESSAGE *message)
            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 */
index ef7d69672b0376cddfab11e36670dc7d4f0f7525..d4aced58e23b32e11aba11628b211cc3f2363bf9 100644 (file)
@@ -146,7 +146,7 @@ void    qmgr_queue_unthrottle(QMGR_QUEUE *queue)
      */
     if (transport->dest_concurrency_limit == 0
        || transport->dest_concurrency_limit > queue->window)
-       if (queue->window <= queue->busy_refcount + transport->init_dest_concurrency)
+       if (queue->window < queue->busy_refcount + transport->init_dest_concurrency)
            queue->window++;
 }
 
@@ -237,6 +237,7 @@ QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site)
     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);
index 7fef4c759f7c78c8f6372262e4d9eced5df4f2f4..204880962b33970e973d0a92a92d79ba624aabce 100644 (file)
@@ -354,10 +354,13 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name)
     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);
index 4f53c3b07f98e8e4fe65f18bb8bf5bc4e6efd9b1..e525247340b527f97a04985108be7296d265a4a2 100644 (file)
@@ -277,7 +277,7 @@ static void super(char **queues, int action)
     argv_free(hash_queue_names);
 }
 
-main(int argc, char **argv)
+int     main(int argc, char **argv)
 {
     int     fd;
     struct stat st;
index e96126266398602a4318a2cddc23fc6e0bc949b0..d6631c0add856c4486dfd145a00d67c9cfa9bdf6 100644 (file)
@@ -55,7 +55,7 @@
 
 /* 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;
index bd6f74959f6f2e55956d6c28637d30fa644504d0..4f31a25b3456e6aae3023c4107b709987da26f71 100644 (file)
@@ -461,7 +461,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
         */
        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;
@@ -577,8 +577,8 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
             * 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);
index c0a433e87b3ae95ba3bf2e3954c645f21273f909..90d9aa96bbc293d78bcc3c958864201b7d2a7e3e 100644 (file)
@@ -179,6 +179,7 @@ smtpd_check.o: ../../include/maps.h
 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
index 6526b86bc1ffd6379073d1ef844c9cf6988cfd96..3de90461a521916b24cbbdb2ddee02727840de80 100644 (file)
@@ -1096,7 +1096,7 @@ static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
     }
     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);
index 1bebfa76d648916506841f873efb2aac55a0282a..32187d3f71cd4dd18ce11280abbe7c55ef8f03bd 100644 (file)
@@ -637,7 +637,7 @@ static int reject_invalid_hostaddr(SMTPD_STATE *state, char *addr,
     /*
      * 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);
@@ -673,7 +673,7 @@ static int reject_invalid_hostname(SMTPD_STATE *state, char *name,
     /*
      * 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);
@@ -709,7 +709,7 @@ static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name,
     /*
      * 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);
@@ -1063,7 +1063,7 @@ static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr,
     /*
      * 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);
index 42c4b254314cbfc19482e7df5063dfdf074e0472..e6ef0ab3518d1b7cfcbf1b750db19141716b0e6c 100644 (file)
@@ -135,7 +135,7 @@ void    smtpd_peer_init(SMTPD_STATE *state)
        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 {
index 2692c6d4fbb73812328c52b1b3a39519a9c469e6..13c0e54ab89ac4b095f1abcf8b0dafc7c3996e4d 100644 (file)
@@ -92,6 +92,7 @@
 #include <stdlib.h>
 #include <lber.h>
 #include <ldap.h>
+#include <string.h>
 
 /* Utility library. */
 
@@ -383,13 +384,16 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
      * 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);
        }
     }
 
index dacb818fe99b54b51978aab7c709d2495864a93d..fcf4a7f7ffb760e01406453dd2f95cb1880b0299 100644 (file)
@@ -66,7 +66,7 @@ const char *get_hostname(void)
        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);
     }
index 29f305fd821eab5f9e8eeb1fbce70547ef7f47c0..fa628c4881c28d4c14929e450bdd5f21c1f13b29 100644 (file)
@@ -6,11 +6,13 @@
 /* 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
@@ -50,7 +56,7 @@
 
 /* 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;
@@ -63,7 +69,8 @@ int     valid_hostname(const char *name)
      * Trivial cases first.
      */
     if (*name == 0) {
-       msg_warn("%s: empty hostname", myname);
+       if (gripe)
+           msg_warn("%s: empty hostname", myname);
        return (0);
     }
 
@@ -76,37 +83,43 @@ int     valid_hostname(const char *name)
                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);
@@ -114,7 +127,7 @@ int     valid_hostname(const char *name)
 
 /* 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";
@@ -129,7 +142,8 @@ int     valid_hostaddr(const char *addr)
      * Trivial cases first.
      */
     if (*addr == 0) {
-       msg_warn("%s: empty address", myname);
+       if (gripe)
+           msg_warn("%s: empty address", myname);
        return (0);
     }
 
@@ -146,28 +160,33 @@ int     valid_hostaddr(const char *addr)
            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);
@@ -195,8 +214,8 @@ int     main(int unused_argc, char **argv)
 
     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);
 }
index 69db140567069a3886d1b9536cf2e2a271481515..8de21619e2624d9b9cb6012c7b897c6a94366af0 100644 (file)
 #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
diff --git a/postfix/src/virtual/.indent.pro b/postfix/src/virtual/.indent.pro
new file mode 120000 (symlink)
index 0000000..5c837ec
--- /dev/null
@@ -0,0 +1 @@
+../../.indent.pro
\ No newline at end of file
diff --git a/postfix/src/virtual/.printfck b/postfix/src/virtual/.printfck
new file mode 100644 (file)
index 0000000..66016ed
--- /dev/null
@@ -0,0 +1,25 @@
+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
diff --git a/postfix/src/virtual/Makefile.in b/postfix/src/virtual/Makefile.in
new file mode 100644 (file)
index 0000000..8bf523e
--- /dev/null
@@ -0,0 +1,166 @@
+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
diff --git a/postfix/src/virtual/deliver_attr.c b/postfix/src/virtual/deliver_attr.c
new file mode 100644 (file)
index 0000000..63b1558
--- /dev/null
@@ -0,0 +1,74 @@
+/*++
+/* 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");
+}
diff --git a/postfix/src/virtual/mailbox.c b/postfix/src/virtual/mailbox.c
new file mode 100644 (file)
index 0000000..5f753fe
--- /dev/null
@@ -0,0 +1,241 @@
+/*++
+/* 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);
+}
diff --git a/postfix/src/virtual/maildir.c b/postfix/src/virtual/maildir.c
new file mode 100644 (file)
index 0000000..6522d28
--- /dev/null
@@ -0,0 +1,172 @@
+/*++
+/* 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);
+}
diff --git a/postfix/src/virtual/recipient.c b/postfix/src/virtual/recipient.c
new file mode 100644 (file)
index 0000000..725108f
--- /dev/null
@@ -0,0 +1,93 @@
+/*++
+/* 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);
+}
diff --git a/postfix/src/virtual/unknown.c b/postfix/src/virtual/unknown.c
new file mode 100644 (file)
index 0000000..8989046
--- /dev/null
@@ -0,0 +1,64 @@
+/*++
+/* 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));
+
+}
diff --git a/postfix/src/virtual/virtual.c b/postfix/src/virtual/virtual.c
new file mode 100644 (file)
index 0000000..90fa45c
--- /dev/null
@@ -0,0 +1,409 @@
+/*++
+/* 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);
+}
diff --git a/postfix/src/virtual/virtual.h b/postfix/src/virtual/virtual.h
new file mode 100644 (file)
index 0000000..1443bc8
--- /dev/null
@@ -0,0 +1,134 @@
+/*++
+/* 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
+/*--*/