]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.0.10-20030524
authorWietse Venema <wietse@porcupine.org>
Sat, 24 May 2003 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:28:53 +0000 (06:28 +0000)
23 files changed:
postfix/HISTORY
postfix/INSTALL
postfix/README_FILES/ADDRESS_VERIFICATION_README
postfix/src/cleanup/cleanup.h
postfix/src/cleanup/cleanup_envelope.c
postfix/src/cleanup/cleanup_extracted.c
postfix/src/cleanup/cleanup_message.c
postfix/src/cleanup/cleanup_state.c
postfix/src/global/cleanup_strerror.c
postfix/src/global/cleanup_user.h
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/global/pipe_command.c
postfix/src/nqmgr/qmgr.h
postfix/src/nqmgr/qmgr_message.c
postfix/src/pickup/pickup.c
postfix/src/qmgr/qmgr_message.c
postfix/src/qmqpd/qmqpd.c
postfix/src/showq/showq.c
postfix/src/smtpd/smtpd.c
postfix/src/smtpd/smtpd_check.c
postfix/src/util/sys_defs.h
postfix/src/util/vstream.c

index baeaeb28a9aa809860afede1f9dc1159fba85c5b..66a4dd33622f87b5c15ffb3b89e6e51e2eb6bbb5 100644 (file)
@@ -8074,6 +8074,17 @@ Apologies for any names omitted.
        descriptor is writable while write() transfers zero bytes.
        File:  util/vstream.c.
 
+20030519
+
+       Feature: new require_{date,from,message_id,received}_header
+       restriction to reject SMTP mail when some message header
+       is missing.  Only the From: and Date: headers are actually
+       required by Internet mail standards; the Received:  and
+       Message-ID: headers are optional, but these are often
+       missing from SPAM email.  Files: global/cleanup_user.h,
+       global/cleanup_strerror.c, smtpd/smtpd_check.c,
+       cleanup/cleanup_message.c.
+
 20030520
 
        Cleanup: future time stamps in Received: headers and negative
@@ -8089,6 +8100,19 @@ Apologies for any names omitted.
        Richard Stockton, Gramma Software. Files:
        cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c.
 
+2003052[34]
+
+       Cleanup: rewrote the queue file record processing loops in
+       cleanup and in [n]qmgr. This code had deteriorated a lot
+       as the result of small changes over the years. This change
+       brings the code closer to "obviously correct". Files:
+       cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c,
+       *qmgr/qmgr_message.c.
+
+       Cleanup: Postfix no longer produces queue files with
+       backwards compatibility for Postfix versions < 1.0 (a.k.a.
+       20010228). File: cleanup/cleanup_extracted.c.
+
 Open problems:
 
        Low: smtp-source may block when sending large test messages.
index b6a3465e9e66db4cce998a23918c550ab517c0ad..8d807539be83a656b81a325a340e629e6d833fc3 100644 (file)
@@ -46,17 +46,17 @@ those embedded manual pages are available in the mantools directory.
 
 If your system is supported, it is one of
 
-    AIX 3.2.5
-    AIX 4.1.x
-    AIX 4.2.0
-    AIX 4.3.x
+    AIX 3.2.5 (long ago)
+    AIX 4.1.x (long ago)
+    AIX 4.2.0 (long ago)
+    AIX 4.3.x (long ago)
     AIX 5.2
-    BSD/OS 2.x
-    BSD/OS 3.x
-    BSD/OS 4.x
-    Darwin 1.x
-    FreeBSD 2.x
-    FreeBSD 3.x
+    BSD/OS 2.x (long ago)
+    BSD/OS 3.x (long ago)
+    BSD/OS 4.x (long ago)
+    Darwin 1.x (long ago)
+    FreeBSD 2.x (long ago)
+    FreeBSD 3.x (long ago)
     FreeBSD 4.x
     FreeBSD 5.x
     HP-UX  9.x
@@ -66,6 +66,7 @@ If your system is supported, it is one of
     IRIX 6.x
     Linux Debian 1.3.1
     Linux Debian 2.x
+    Linux Debian 3.x
     Linux RedHat 3.x (August 2002)
     Linux RedHat 4.x
     Linux RedHat 5.x
index 410fa8faee622cf59e7051f3e813c950d56481a2..f9a5aeca4b2fc4d976fef1f38cf5944cc2ca9f7e 100644 (file)
@@ -5,7 +5,7 @@ Sender address verification blocks mail from an unknown sender
 address until after the address is verified.  An address is verified
 by probing the nearest MTA for that address, without actually
 delivering mail to it (SMTP interruptus). Probe messages are like
-normail mail, but are discarded instead of being deferred or bounced.
+normal mail, but are discarded instead of being deferred or bounced.
 
 The same technique may be useful to block mail for undeliverable
 recipients, for example on mail relay hosts that do not have a copy
@@ -16,31 +16,95 @@ a short delay of up to 9 seconds while an address is verified for
 the first time.  Once an address status is known, the status is
 cached and Postfix replies immediately.  When verification takes
 longer than 9 seconds the Postfix SMTP server defers the sender or
-recipient address message with a 450 reply. Normal mail clients
-will connect again after some delay.
+recipient address with a 450 reply. Normal mail clients will connect
+again after some delay.
 
-Sender and recipient address verification are turned on with the
-"reject_unverified_sender" and "reject_unverified_recipient"
-restrictions, respectively.
+Limitations
+===========
+
+Postfix probes the nearest MTA for the address that is being
+verified, without actually sending mail to that address. If the
+nearest MTA accepts the recipient, then Postfix assumes that the
+address is deliverable, even when the address will bounce AFTER
+that MTA accepts it.
+
+Unfortunately, some major sites such as YAHOO do not reject unknown
+addresses in reply to the RCPT TO command, but report a delivery
+failure in response to "end of data" after a message is transferred.
+Postfix address verification does not work with such sites.
+
+By default, Postfix probe messages have "postmaster@$myorigin" as
+the sender address. You can change this into the null address
+(address_verify_sender =) but that causes address probes to fail
+with mis-configured sites that reject MAIL FROM: <>.
+
+Turning on recipient address verification
+=========================================
+
+Recipient address verification may be useful to block mail for
+undeliverable recipients on mail relay hosts that do not have a
+copy of all the relayed recipient addresses. This prevents the mail
+queue from filling up with undeliverable and bounced SPAM.
 
 Recipient address verification is relatively straightforward and
 there are no surprises. If a recipient probe fails, then Postfix
 rejects mail for the recipient address.  If a recipient probe
 succeeds, then Postfix accepts mail for the recipient address.
 
-Turning on sender address verification
-======================================
+/etc/postfix/main.cf:
+    smtpd_recipient_restrictions = 
+       permit_mynetworks
+       reject_unauth_destination
+       ...
+       reject_unknown_recipient_domain
+       reject_unverified_recipient
+       ...
+
+The "reject_unknown_recipient_domain" restriction blocks mail for
+non-existent domains. Putting this before "reject_unverified_recipient"
+avoids the overhead of generating unnecessary probe messages.
+
+The unverified_recipient_reject_code parameter (default 450)
+specifies how Postfix replies when a recipient address is known to
+bounce.  Change this setting into 550 when you trust Postfix's
+judgments.
+
+Sender address verification for mail from frequently forged domains
+===================================================================
+
+It is relatively safe to turn on sender address verification for
+specific domains that often appear in forged email.
+
+/etc/postfix/main.cf:
+    smtpd_sender_restrictions = hash:/etc/postfix/sender_access
+    unverified_sender_reject_code = 550
+/etc/postfix/sender_access:
+    aol.com     reject_unverified_sender
+    hotmail.com reject_unverified_sender
+    bigfoot.com reject_unverified_sender
+    ... etcetera ...
+
+A list of frequently forged MAIL FROM domains can be found at
+http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in
+
+NOTE: One of the first things you might want to do is to turn on
+sender address verification for all your own domains.
+
+Turning on sender address verification for all email
+====================================================
 
 Unfortunately, sender address verification cannot simply be turned
-on - you are likely to lose legitimate mail from mis-configured
-systems. You will almost certainly have to set up white lists for
-specific addresses or even for entire domains.
+on for all email - you are likely to lose legitimate mail from
+mis-configured systems. You almost certainly will have to set up
+white lists for specific addresses, or even for entire domains.
 
 To find out how sender address verification would affect your mail,
 specify "warn_if_reject reject_unverified_sender" so that you can
 see what mail would be blocked:
 
     smtpd_sender_restrictions = 
+       permit_mynetworks
        ... 
        check_sender_access hash:/etc/postfix/sender_access
        reject_unknown_sender_domain
@@ -50,9 +114,20 @@ see what mail would be blocked:
 This is also a good way to populate your cache with address
 verification results before you start to actually reject mail.
 
-The sender_access restriction is needed to whitelist domains that
-are known to be OK. See the section titled "Limitations" at the
-end of this document.
+The sender_access restriction is needed to whitelist domains or
+addresses that are known to be OK.  Although Postfix will not mark
+a known-to-be-good address as bad after a probe fails, it is better
+to be safe than sorry.
+
+NOTE: You will have to whitelist sites such as securityfocus.com
+and other sites that operate mailing lists that use a different
+sender address for each posting (VERP).  Such addresses pollute
+the address verification cache quickly, and generate unnecessary
+sender verification probes.
+
+/etc/postfix/sender_access
+    securityfocus.com
+    ...
 
 The "reject_unknown_sender_domain" restriction blocks mail from
 non-existent domains. Putting this before "reject_unverified_sender"
@@ -92,39 +167,18 @@ the next section. Meanwhile you will not receive mail via SMTP.
 
 The verify daemon process will create a new database when none
 exists, and will open/create the file before it enters the chroot
-jail or before it drops root privileges.
+jail and before it drops root privileges.
 
 Managing the address verification database
 ==========================================
 
+The verify(8) manual page describes parameters that control how
+long information remains cached before it needs to be refreshed,
+and how long information can remain "unrefreshed" before it expires.
+Postfix uses different controls for positive results (address was
+accepted) and for negative results (address was rejected).
+
 Right now, no tools are provided to manage the address verification
 database. If the file gets too big, or if it gets corrupted, you
 can manually delete the file and run "postfix reload".  The new
 verify daemon process will then create a new, empty, database.
-
-Limitations
-===========
-
-Postfix probes the nearest MTA for the sender domain without actually
-sending mail. If that MTA accepts the recipient, then Postfix
-assumes that the address is deliverable, even when the address will
-bounce AFTER that MTA accepts it.
-
-You will probably want to put a whitelist before the address
-verification restriction, so that you can exclude known to be OK
-domains or addresses from verification. Although Postfix will not
-mark a known-to-be-good address as bad after a probe fails, it is
-better to be safe than sorry.
-
-    smtpd_sender_restrictions =
-       ...
-       check_sender_access hash:/etc/postfix/sender_access
-       reject_unknown_sender_domain
-       reject_unverified_sender
-       ...
-
-NOTE: You will have to whitelist sites such as securityfocus.com
-and other sites that operate mailing lists that use a different
-sender address for each posting (VERP).  Such addresses pollute
-the address verification cache quickly, and generate unnecessary
-sender verification probes.
index c1a1c2fab83e2b51af6c190b9728f70e18a99ab1..ceaf212efac5d18fe6e18cd3d1f376abbecddc3f 100644 (file)
@@ -56,9 +56,9 @@ typedef struct CLEANUP_STATE {
     BH_TABLE *dups;                    /* recipient dup filter */
     long    warn_time;                 /* cleanup_envelope.c */
     void    (*action) (struct CLEANUP_STATE *, int, const char *, int);
-    off_t   mesg_offset;               /* start of message segment */
     off_t   data_offset;               /* start of message content */
-    off_t   xtra_offset;               /* start of extra segment */
+    off_t   xtra_offset;               /* start of extracted content */
+    int     verp_seen;                 /* REC_TYPE_VERP seen */
     int     end_seen;                  /* REC_TYPE_END seen */
     int     rcpt_count;                        /* recipient count */
     char   *reason;                    /* failure reason */
index 8cdefbd00d891f8ba50c190d4553e06d0de41ba0..a22bc6e9868301c1894c1fc1cf0822ed08a16357 100644 (file)
@@ -87,13 +87,13 @@ void    cleanup_envelope(CLEANUP_STATE *state, int type,
     /*
      * The message size and count record goes first, so it can easily be
      * updated in place. This information takes precedence over any size
-     * estimate provided by the client. It's all in one record for forward
-     * compatibility so we can switch back to an older Postfix version.
+     * estimate provided by the client. It's all in one record, data size
+     * first, for backwards compatibility reasons.
      */
     cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
-                      (REC_TYPE_SIZE_CAST1) 0,
-                      (REC_TYPE_SIZE_CAST2) 0,
-                      (REC_TYPE_SIZE_CAST3) 0);
+                      (REC_TYPE_SIZE_CAST1) 0, /* content size */
+                      (REC_TYPE_SIZE_CAST2) 0, /* content offset */
+                      (REC_TYPE_SIZE_CAST3) 0);        /* recipient count */
 
     /*
      * Pass control to the actual envelope processing routine.
@@ -112,68 +112,28 @@ static void cleanup_envelope_process(CLEANUP_STATE *state, int type,
     const char *error_text;
     int     extra_flags;
 
-    /*
-     * On the transition from envelope segment to content segment, do some
-     * sanity checks and add some records.
-     */
-    if (type == REC_TYPE_MESG) {
-       if (state->sender == 0 || state->time == 0) {
-           msg_warn("%s: missing sender or time envelope record",
-                    state->queue_id);
-           state->errs |= CLEANUP_STAT_BAD;
-       } else {
-           if (state->warn_time == 0 && var_delay_warn_time > 0)
-               state->warn_time = state->time + var_delay_warn_time;
-           if (state->warn_time)
-               cleanup_out_format(state, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
-                                  state->warn_time);
-           state->action = cleanup_message;
-       }
-       return;
-    }
+    if (msg_verbose)
+       msg_info("initial envelope %c %.*s", type, len, buf);
+
     if (type == REC_TYPE_FLGS) {
-       if (msg_verbose)
-           msg_info("envelope %c %.*s", type, len, buf);
+       /* Not part of queue file format. */
        extra_flags = atol(buf);
        if (extra_flags & ~CLEANUP_FLAG_MASK_EXTRA)
-           msg_warn("%s: bad extra flags: 0x%x", state->queue_id, extra_flags);
+           msg_warn("%s: ignoring bad extra flags: 0x%x",
+                    state->queue_id, extra_flags);
        else
            state->flags |= extra_flags;
        return;
     }
     if (strchr(REC_TYPE_ENVELOPE, type) == 0) {
-       msg_warn("%s: unexpected record type %d in envelope",
+       msg_warn("%s: unexpected record type %d in envelope: message rejected",
                 state->queue_id, type);
        state->errs |= CLEANUP_STAT_BAD;
        return;
     }
-    if (msg_verbose)
-       msg_info("envelope %c %.*s", type, len, buf);
-
-    if (type != REC_TYPE_RCPT) {
-       if (state->orig_rcpt != 0) {
-           if (type != REC_TYPE_DONE)
-               msg_warn("%s: out-of-order original recipient record <%.200s>",
-                        state->queue_id, state->orig_rcpt);
-           myfree(state->orig_rcpt);
-           state->orig_rcpt = 0;
-       }
-    }
-    if (type == REC_TYPE_TIME) {
-       state->time = atol(buf);
-       cleanup_out(state, type, buf, len);
-    } else if (type == REC_TYPE_FULL) {
-       state->fullname = mystrdup(buf);
-    } else if (type == REC_TYPE_FROM) {
-       if (state->sender != 0) {
-           msg_warn("%s: too many envelope sender records", state->queue_id);
-           state->errs |= CLEANUP_STAT_BAD;
-           return;
-       }
-       cleanup_addr_sender(state, buf);
-    } else if (type == REC_TYPE_RCPT) {
+    if (type == REC_TYPE_RCPT) {
        if (state->sender == 0) {               /* protect showq */
-           msg_warn("%s: envelope recipient precedes sender",
+           msg_warn("%s: envelope recipient precedes sender: message rejected",
                     state->queue_id);
            state->errs |= CLEANUP_STAT_BAD;
            return;
@@ -183,38 +143,92 @@ static void cleanup_envelope_process(CLEANUP_STATE *state, int type,
        cleanup_addr_recipient(state, buf);
        myfree(state->orig_rcpt);
        state->orig_rcpt = 0;
-    } else if (type == REC_TYPE_DONE) {
-        /* void */ ;
-    } else if (type == REC_TYPE_WARN) {
-       if ((state->warn_time = atol(buf)) < 0) {
-           state->errs |= CLEANUP_STAT_BAD;
-           return;
+       return;
+    }
+    if (type == REC_TYPE_DONE) {
+       if (state->orig_rcpt != 0) {
+           myfree(state->orig_rcpt);
+           state->orig_rcpt = 0;
        }
-    } else if (type == REC_TYPE_VERP) {
-       if (state->sender == 0 || *state->sender == 0) {
-           state->errs |= CLEANUP_STAT_BAD;
-           return;
+       return;
+    }
+    if (state->orig_rcpt != 0) {
+       /* REC_TYPE_ORCP must be followed by REC_TYPE_RCPT or REC_TYPE DONE. */
+       msg_warn("%s: out-of-order original recipient record <%.200s>",
+                state->queue_id, state->orig_rcpt);
+       myfree(state->orig_rcpt);
+       state->orig_rcpt = 0;
+    }
+    if (type == REC_TYPE_ORCP) {
+       state->orig_rcpt = mystrdup(buf);
+       return;
+    }
+    if (type == REC_TYPE_TIME) {
+       /* First definition wins. */
+       if (state->time == 0) {
+           state->time = atol(buf);
+           cleanup_out(state, type, buf, len);
        }
-       if (verp_delims_verify(buf) == 0) {
+       return;
+    }
+    if (type == REC_TYPE_FULL) {
+       /* First definition wins. */
+       if (state->fullname == 0) {
+           state->fullname = mystrdup(buf);
            cleanup_out(state, type, buf, len);
-       } else {
-           msg_warn("%s: bad VERP delimiters: \"%s\"", state->queue_id, buf);
+       }
+       return;
+    }
+    if (type == REC_TYPE_FROM) {
+       /* Allow only one instance. */
+       if (state->sender != 0) {
+           msg_warn("%s: too many envelope sender records: message rejected",
+                    state->queue_id);
            state->errs |= CLEANUP_STAT_BAD;
            return;
        }
-    } else if (type == REC_TYPE_ATTR) {
+       cleanup_addr_sender(state, buf);
+       return;
+    }
+    if (type == REC_TYPE_WARN) {
+       /* First definition wins. */
+       if (state->warn_time == 0) {
+           if ((state->warn_time = atol(buf)) < 0) {
+               msg_warn("%s: bad arrival time record: %s: message rejected",
+                        state->queue_id, buf);
+               state->errs |= CLEANUP_STAT_BAD;
+           }
+       }
+       return;
+    }
+    if (type == REC_TYPE_VERP) {
+       /* First definition wins. */
+       if (state->verp_seen == 0) {
+           if ((error_text = verp_delims_verify(buf)) != 0) {
+               msg_warn("%s: %s: \"%s\": message rejected",
+                        state->queue_id, error_text, buf);
+               state->errs |= CLEANUP_STAT_BAD;
+               return;
+           }
+           state->verp_seen = 1;
+           cleanup_out(state, type, buf, len);
+       }
+       return;
+    }
+    if (type == REC_TYPE_ATTR) {
+       /* Pass through. Last definition wins. */
        char   *sbuf;
 
        if (state->attr->used >= var_qattr_count_limit) {
-           msg_warn("%s: queue file attribute count exceeds safety limit: %d",
+           msg_warn("%s: queue file attribute count exceeds safety limit %d"
+                    ": message rejected",
                     state->queue_id, var_qattr_count_limit);
            state->errs |= CLEANUP_STAT_BAD;
            return;
        }
-       cleanup_out(state, type, buf, len);
        sbuf = mystrdup(buf);
        if ((error_text = split_nameval(sbuf, &attr_name, &attr_value)) != 0) {
-           msg_warn("%s: malformed attribute: %s: %.100s",
+           msg_warn("%s: malformed attribute: %s: %.100s: message rejected",
                     state->queue_id, error_text, buf);
            state->errs |= CLEANUP_STAT_BAD;
            myfree(sbuf);
@@ -222,9 +236,47 @@ static void cleanup_envelope_process(CLEANUP_STATE *state, int type,
        }
        nvtable_update(state->attr, attr_name, attr_value);
        myfree(sbuf);
-    } else if (type == REC_TYPE_ORCP) {
-       state->orig_rcpt = mystrdup(buf);
-    } else {
        cleanup_out(state, type, buf, len);
+       return;
+    }
+    if (type == REC_TYPE_SIZE)
+       /* Use our own SIZE record instead. */
+       return;
+    if (type != REC_TYPE_MESG) {
+       /* Anything else. Pass through. */
+       cleanup_out(state, type, buf, len);
+       return;
+    }
+
+    /*
+     * On the transition from envelope segment to content segment, do some
+     * sanity checks.
+     * 
+     * If senders can be specified in the extracted envelope segment, then we
+     * need to move the VERP test there, too.
+     */
+    if (state->sender == 0 || state->time == 0) {
+       msg_warn("%s: missing sender or time envelope record: message rejected",
+                state->queue_id);
+       state->errs |= CLEANUP_STAT_BAD;
+       return;
+    }
+    if (state->verp_seen && (state->sender == 0 || *state->sender == 0)) {
+       msg_warn("%s: VERP request with no or null sender: message rejected",
+                state->queue_id);
+       state->errs |= CLEANUP_STAT_BAD;
+       return;
     }
+
+    /*
+     * Emit records for information that we collected from the envelope
+     * segment.
+     */
+    if (state->warn_time == 0 && var_delay_warn_time > 0)
+       state->warn_time = state->time + var_delay_warn_time;
+    if (state->warn_time)
+       cleanup_out_format(state, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
+                          state->warn_time);
+
+    state->action = cleanup_message;
 }
index 2d284b2727e15144edde9db023a13af35b7e7a30..e218fb72ed1432fa06f26264e5b47974d4bc3c00 100644 (file)
@@ -43,6 +43,7 @@
 #include <sys_defs.h>
 #include <unistd.h>
 #include <errno.h>
+#include <string.h>
 
 /* Utility library. */
 
 
 static void cleanup_extracted_process(CLEANUP_STATE *, int, const char *, int);
 
+ /*
+  * The following queue file records are generated from message header or
+  * message body content. We may encounter them in extracted envelope
+  * segments after mail is re-injected with "postsuper -r" and we should
+  * ignore them. It might be infinitesimally faster to move this test to the
+  * pickup daemon, but that would make program maintenance more difficult.
+  */
+static char cleanup_extracted_generated[] = {
+    REC_TYPE_RRTO,                     /* return-receipt-to */
+    REC_TYPE_ERTO,                     /* errors-to */
+    REC_TYPE_FILT,                     /* content filter */
+    REC_TYPE_INSP,                     /* content inspector */
+    REC_TYPE_RDR,                      /* redirect address */
+    REC_TYPE_ATTR,                     /* some header attribute */
+    0,
+};
+
 /* cleanup_extracted - initialize extracted segment */
 
 void    cleanup_extracted(CLEANUP_STATE *state, int type,
@@ -83,42 +101,84 @@ void    cleanup_extracted(CLEANUP_STATE *state, int type,
     cleanup_out_string(state, REC_TYPE_XTRA, "");
 
     /*
-     * Put the optional content filter before the mandatory Return-Receipt-To
-     * and Errors-To so that the queue manager will pick up the filter name
-     * before starting deliveries.
+     * Postfix keeps all information related to an email message is in a
+     * write-once file, including the envelope sender and recipients, and the
+     * message content. This design maximizes robustness: one file is easier
+     * to keep track of than multiple files, and write-once means that no
+     * operation ever needs to be undone. This design also minimizes file
+     * system overhead, because creating and removing files is relatively
+     * expensive compared to writing files. Separate files are used for
+     * logging the causes of deferral or failed delivery.
+     * 
+     * A Postfix queue file consists of three segments.
+     * 
+     * 1) The initial envelope segment with the arrival time, sender address,
+     * recipients, and some other stuff that can be recorded before the
+     * message content is received, including non-recipient information that
+     * results from actions in Postfix SMTP server access tables. In this
+     * segment, recipient records may be preceded or followed by
+     * non-recipient records.
+     * 
+     * 2) The message content segment with the message headers and body. The
+     * message body includes all the MIME segments, if there are any.
+     * 
+     * 3) The extracted envelope segment with information that was extracted
+     * from message headers or from the message body, including recipient
+     * addresses that were extracted from message headers, and non-recipient
+     * information that results from actions in header/body_checks patterns.
+     * In this segment, all non-recipient records precede the recipient
+     * records.
+     * 
+     * There are two queue file layouts.
+     * 
+     * A) All recipient records are in the initial envelope segment, except for
+     * the optional always_bcc recipient which is always stored in the
+     * extracted envelope segment. The queue manager reads as many recipients
+     * as it can from the initial envelope segment, and then examines all
+     * remaining initial envelope records and all extracted envelope records,
+     * picking up non-recipient information. This organization favors
+     * messages with fewer than $qmgr_active_recipient_limit recipients.
+     * 
+     * B) All recipient records are stored in the extracted envelope segment,
+     * after all non-recipient records. The queue manager is guaranteed to
+     * have read all the non-recipient records before it sees the first
+     * recipient record. This organization can handle messages with very
+     * large numbers of recipients.
+     * 
+     * All this is the result of an evolutionary process, where compatibility
+     * between Postfix versions was a major goal as new features were added.
+     * Therefore the file organization is not optimal from a performance
+     * point of view. In hindsight, the non-recipient information that
+     * follows recipients in the initial envelope segment could be moved to
+     * the extracted envelope segment. This would improve file organization
+     * A)'s performance with very large numbers of recipients, by eliminating
+     * the need to examine all initial envelope records before starting
+     * deliveries.
      */
     if (state->filter != 0)
        cleanup_out_string(state, REC_TYPE_FILT, state->filter);
 
     /*
-     * Output the optional redirect target address before the mandatory
-     * Return-Receipt-To and Errors-To queue file records so that the queue
-     * manager will pick up the address before starting deliveries.
+     * The optional redirect target address from header/body_checks actions.
      */
     if (state->redirect != 0)
        cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
 
     /*
-     * Older Postfix versions didn't emit encoding information, so this
-     * record can only be optional. Putting this before the mandatory
-     * Return-Receipt-To and Errors-To ensures that the queue manager will
-     * pick up the content encoding before starting deliveries.
+     * Older Postfix versions didn't MIME emit encoding information, so this
+     * record can only be optional.
      */
     if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) != 0)
        cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s",
                           MAIL_ATTR_ENCODING, encoding);
 
     /*
-     * Always emit Return-Receipt-To and Errors-To records, and always emit
-     * them ahead of extracted recipients, so that the queue manager does not
-     * waste lots of time searching through large numbers of recipient
-     * addresses.
+     * Return-Receipt-To and Errors-To records are now optional.
      */
-    cleanup_out_string(state, REC_TYPE_RRTO, state->return_receipt ?
-                      state->return_receipt : "");
-
-    cleanup_out_string(state, REC_TYPE_ERTO, state->errors_to ?
-                      state->errors_to : state->sender);
+    if (state->return_receipt)
+       cleanup_out_string(state, REC_TYPE_RRTO, state->return_receipt);
+    if (state->errors_to)
+       cleanup_out_string(state, REC_TYPE_ERTO, state->errors_to);
 
     /*
      * Pass control to the routine that processes the extracted segment.
@@ -134,17 +194,14 @@ static void cleanup_extracted_process(CLEANUP_STATE *state, int type,
 {
     char   *myname = "cleanup_extracted_process";
 
-    /*
-     * Weird condition for consistency with cleanup_envelope.c
-     */
-    if (type != REC_TYPE_RCPT) {
-       if (state->orig_rcpt != 0) {
-           if (type != REC_TYPE_DONE)
-               msg_warn("%s: out-of-order original recipient record <%.200s>",
-                        state->queue_id, buf);
-           myfree(state->orig_rcpt);
-           state->orig_rcpt = 0;
-       }
+    if (msg_verbose)
+       msg_info("extracted envelope %c %.*s", type, len, buf);
+
+    if (strchr(REC_TYPE_EXTRACT, type) == 0) {
+       msg_warn("%s: unexpected record type %d in extracted envelope"
+                ": message rejected", state->queue_id, type);
+       state->errs |= CLEANUP_STAT_BAD;
+       return;
     }
     if (type == REC_TYPE_RCPT) {
        if (state->orig_rcpt == 0)
@@ -153,13 +210,30 @@ static void cleanup_extracted_process(CLEANUP_STATE *state, int type,
        myfree(state->orig_rcpt);
        state->orig_rcpt = 0;
        return;
-    } else if (type == REC_TYPE_DONE) {
+    }
+    if (type == REC_TYPE_DONE) {
+       if (state->orig_rcpt != 0) {
+           myfree(state->orig_rcpt);
+           state->orig_rcpt = 0;
+       }
        return;
-    } else if (type == REC_TYPE_ORCP) {
+    }
+    if (state->orig_rcpt != 0) {
+       /* REC_TYPE_ORCP must be followed by REC_TYPE_RCPT or REC_TYPE DONE. */
+       msg_warn("%s: out-of-order original recipient record <%.200s>",
+                state->queue_id, buf);
+       myfree(state->orig_rcpt);
+       state->orig_rcpt = 0;
+    }
+    if (type == REC_TYPE_ORCP) {
        state->orig_rcpt = mystrdup(buf);
        return;
     }
+    if (strchr(cleanup_extracted_generated, type) != 0)
+       /* Use our own message header extracted information instead. */
+       return;
     if (type != REC_TYPE_END) {
+       msg_warn("unexpected non-recipient record: %s", rec_type_name(type));
        cleanup_out(state, type, buf, len);
        return;
     }
@@ -195,8 +269,7 @@ static void cleanup_extracted_process(CLEANUP_STATE *state, int type,
 
     /*
      * Update the preliminary message size and count fields with the actual
-     * values.  For forward compatibility, we put the info into one record
-     * (so that it is possible to switch back to an older Postfix version).
+     * values.
      */
     if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
        msg_fatal("%s: vstream_fseek %s: %m", myname, cleanup_path);
@@ -204,15 +277,4 @@ static void cleanup_extracted_process(CLEANUP_STATE *state, int type,
            (REC_TYPE_SIZE_CAST1) (state->xtra_offset - state->data_offset),
                       (REC_TYPE_SIZE_CAST2) state->data_offset,
                       (REC_TYPE_SIZE_CAST3) state->rcpt_count);
-
-    /*
-     * Update the preliminary start-of-content marker with the actual value.
-     * For forward compatibility, we keep this information until the end of
-     * the year 2002 (so that it is possible to switch back to an older
-     * Postfix version).
-     */
-    if (vstream_fseek(state->dst, state->mesg_offset, SEEK_SET) < 0)
-       msg_fatal("%s: vstream_fseek %s: %m", myname, cleanup_path);
-    cleanup_out_format(state, REC_TYPE_MESG, REC_TYPE_MESG_FORMAT,
-                      (REC_TYPE_MESG_CAST) state->xtra_offset);
 }
index 72a562703c53a238c89e3dac261a7770b9b8ae19..fc4868fd4d225b07494b447b2dad81c5dc4d3a69 100644 (file)
@@ -493,6 +493,29 @@ static void cleanup_header_callback(void *context, int header_class,
     }
 }
 
+/* cleanup_missing - handle missing message header */
+
+static void cleanup_missing(CLEANUP_STATE *state, const char *resent,
+                                   const char *header)
+{
+    const char *attr;
+
+    if ((attr = nvtable_find(state->attr, MAIL_ATTR_ORIGIN)) == 0)
+       attr = "unknown";
+    vstring_sprintf(state->temp1, "%s: reject: missing %s%s header from %s;",
+                   state->queue_id, resent, header, attr);
+    if (state->sender)
+       vstring_sprintf_append(state->temp1, " from=<%s>", state->sender);
+    if (state->recip)
+       vstring_sprintf_append(state->temp1, " to=<%s>", state->recip);
+    if ((attr = nvtable_find(state->attr, MAIL_ATTR_PROTO_NAME)) != 0)
+       vstring_sprintf_append(state->temp1, " proto=%s", attr);
+    if ((attr = nvtable_find(state->attr, MAIL_ATTR_HELO_NAME)) != 0)
+       vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
+    msg_info("%s", vstring_str(state->temp1));
+    state->errs |= CLEANUP_STAT_MISS_HDR;
+}
+
 /* cleanup_header_done_callback - insert missing message headers */
 
 static void cleanup_header_done_callback(void *context)
@@ -502,6 +525,15 @@ static void cleanup_header_done_callback(void *context)
     struct tm *tp;
     TOK822 *token;
 
+    /*
+     * Postfix prepends a Received: message header, so we should see two when
+     * one is required.
+     */
+    if ((state->flags & CLEANUP_FLAG_NEED_RCVD) && state->hop_count < 2) {
+       cleanup_missing(state, "", "Received");
+       return;
+    }
+
     /*
      * Add a missing (Resent-)Message-Id: header. The message ID gives the
      * time in GMT units, plus the local queue ID.
@@ -513,6 +545,10 @@ static void cleanup_header_done_callback(void *context)
      */
     if ((state->headers_seen & (1 << (state->resent[0] ?
                           HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) {
+       if (state->flags & CLEANUP_FLAG_NEED_MSGID) {
+           cleanup_missing(state, state->resent, "Message-Id");
+           return;
+       }
        tp = gmtime(&state->time);
        strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp);
        cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s.%s@%s>",
@@ -528,6 +564,10 @@ static void cleanup_header_done_callback(void *context)
      */
     if ((state->headers_seen & (1 << (state->resent[0] ?
                                      HDR_RESENT_DATE : HDR_DATE))) == 0) {
+       if (state->flags & CLEANUP_FLAG_NEED_DATE) {
+           cleanup_missing(state, state->resent, "Date");
+           return;
+       }
        cleanup_out_format(state, REC_TYPE_NORM, "%sDate: %s",
                           state->resent, mail_date(state->time));
     }
@@ -537,6 +577,10 @@ static void cleanup_header_done_callback(void *context)
      */
     if ((state->headers_seen & (1 << (state->resent[0] ?
                                      HDR_RESENT_FROM : HDR_FROM))) == 0) {
+       if (state->flags & CLEANUP_FLAG_NEED_FROM) {
+           cleanup_missing(state, state->resent, "From");
+           return;
+       }
        quote_822_local(state->temp1, *state->sender ?
                        state->sender : MAIL_ADDR_MAIL_DAEMON);
        vstring_sprintf(state->temp2, "%sFrom: %s",
@@ -648,7 +692,8 @@ static void cleanup_message_headerbody(CLEANUP_STATE *state, int type,
      * This should never happen.
      */
     else {
-       msg_warn("%s: unexpected record type: %d", myname, type);
+       msg_warn("%s: unexpected record type in message content: %d"
+                ": message rejected", myname, type);
        state->errs |= CLEANUP_STAT_BAD;
     }
 }
@@ -682,13 +727,9 @@ void    cleanup_message(CLEANUP_STATE *state, int type, const char *buf, int len
     int     mime_options;
 
     /*
-     * Write a dummy start-of-content segment marker. We'll update it with
-     * real file offset information after reaching the end of the message
-     * content.
+     * Write the start-of-content segment marker. 
      */
-    if ((state->mesg_offset = vstream_ftell(state->dst)) < 0)
-       msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
-    cleanup_out_format(state, REC_TYPE_MESG, REC_TYPE_MESG_FORMAT, 0L);
+    cleanup_out_string(state, REC_TYPE_MESG, "");
     if ((state->data_offset = vstream_ftell(state->dst)) < 0)
        msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
 
index f10d50c3d20d6bc097305a50bf1a4b95ef8fd605..a03ae54d16aa0e0293d400793da44a05b034a41f 100644 (file)
@@ -79,9 +79,9 @@ CLEANUP_STATE *cleanup_state_alloc(void)
     state->dups = been_here_init(var_dup_filter_limit, BH_FLAG_FOLD);
     state->warn_time = 0;
     state->action = cleanup_envelope;
-    state->mesg_offset = -1;
     state->data_offset = -1;
     state->xtra_offset = -1;
+    state->verp_seen = 0;
     state->end_seen = 0;
     state->rcpt_count = 0;
     state->reason = 0;
index 489093ed94e2e9cb367d099e611be3d980c5703e..e763ce950f9c528dbdd6b08f255dafcdcafab8fd 100644 (file)
@@ -51,6 +51,7 @@ static struct cleanup_stat_map cleanup_stat_map[] = {
     CLEANUP_STAT_BAD, "Internal protocol error",
     CLEANUP_STAT_RCPT, "No recipients specified",
     CLEANUP_STAT_HOPS, "Too many hops",
+    CLEANUP_STAT_MISS_HDR, "Missing message header",
     CLEANUP_STAT_SIZE, "Message file too big",
     CLEANUP_STAT_CONT, "Message content rejected",
     CLEANUP_STAT_WRITE, "Error writing message file",
index ce4af58514d7b83c39477416346281d61b347cd5..8c988251b9b5d7e6a51b648a0dbcfda89a136894 100644 (file)
 #define CLEANUP_FLAG_HOLD      (1<<2)  /* Place message on hold */
 #define CLEANUP_FLAG_DISCARD   (1<<3)  /* Discard message silently */
 #define CLEANUP_FLAG_BCC_OK    (1<<4)  /* Ok to add auto-BCC addresses */
+#define CLEANUP_FLAG_NEED_DATE (1<<5)  /* Require (Resent:-)Date: */
+#define CLEANUP_FLAG_NEED_FROM (1<<6)  /* Require (Resent:-)From: */
+#define CLEANUP_FLAG_NEED_MSGID        (1<<7)  /* Require (Resent:-)Message-Id: */
+#define CLEANUP_FLAG_NEED_RCVD (1<<8)  /* Require two Received: headers */
 
  /*
   * These are set on the fly while processing SMTP envelopes or message
   * content.
   */
 #define CLEANUP_FLAG_MASK_EXTRA \
-       (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)
+       (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD | CLEANUP_FLAG_NEED_DATE | \
+           CLEANUP_FLAG_NEED_FROM | CLEANUP_FLAG_NEED_MSGID | \
+           CLEANUP_FLAG_NEED_RCVD)
 
  /*
   * Diagnostics.
@@ -39,6 +45,7 @@
 #define CLEANUP_STAT_SIZE      (1<<2)  /* Message file too big */
 #define CLEANUP_STAT_CONT      (1<<3)  /* Message content rejected */
 #define CLEANUP_STAT_HOPS      (1<<4)  /* Too many hops */
+#define CLEANUP_STAT_MISS_HDR  (1<<5)  /* Some missing header */
 #define CLEANUP_STAT_RCPT      (1<<6)  /* No recipients found */
 
  /*
index 3c8021d5d8f9092bcc13342ccf4a9ef0695956db..4b968416c5e9738cd804967b2710228764631a4c 100644 (file)
@@ -1337,6 +1337,11 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`\
 abcdefghijklmnopqrstuvwxyz{|}~"
 extern char *var_smtpd_exp_filter;
 
+#define REQUIRE_DATE_HDR       "require_date_header"
+#define REQUIRE_FROM_HDR       "require_from_header"
+#define REQUIRE_MSGID_HDR      "require_message_id_header"
+#define REQUIRE_RCVD_HDR       "require_received_header"
+
  /*
   * Heuristic to reject unknown local recipients at the SMTP port.
   */
index f649ecd360b87ff1687d29e1f9da2e660e2d69d6..700a85cf488d7692c68c0340962a54848eb48a17 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change the patchlevel and the release date. Snapshots change the
   * release date only, unless they include the same bugfix as a patch release.
   */
-#define MAIL_RELEASE_DATE      "20030521"
+#define MAIL_RELEASE_DATE      "20030524"
 
 #define VAR_MAIL_VERSION       "mail_version"
 #define DEF_MAIL_VERSION       "2.0.10-" MAIL_RELEASE_DATE
index c3ca41b4a49bfd564eda648bcc270d655f1f7924..550f87c3c6db6c1d214bfaeea26a2a49e93822ba 100644 (file)
@@ -379,11 +379,16 @@ int     pipe_command(VSTREAM *src, VSTRING *why,...)
      * 
      * Turn on non-blocking writes to the child process so that we can enforce
      * timeouts after partial writes.
+     * 
+     * XXX This breaks on IRIX where select reports that a pipe is writable
+     * while write transfers zero bytes.
      */
     if (pipe(cmd_in_pipe) < 0 || pipe(cmd_out_pipe) < 0)
        msg_fatal("%s: pipe: %m", myname);
     non_blocking(cmd_out_pipe[1], NON_BLOCKING);
+#ifndef BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE
     non_blocking(cmd_in_pipe[1], NON_BLOCKING);
+#endif
 
     /*
      * Spawn off a child process and irrevocably change privilege to the
index 65037f65713852852f9587842fff03348f4f944f..34b54ffcdd5ef266fbcd8f1518ad5a60f6f9d5d1 100644 (file)
@@ -263,6 +263,7 @@ struct QMGR_MESSAGE {
     long    warn_offset;               /* warning bounce flag offset */
     time_t  warn_time;                 /* time next warning to be sent */
     long    data_offset;               /* data seek offset */
+    long    extra_offset;              /* extracted data seek offset */
     char   *queue_name;                        /* queue name */
     char   *queue_id;                  /* queue file */
     char   *encoding;                  /* content encoding */
@@ -275,7 +276,6 @@ struct QMGR_MESSAGE {
     char   *redirect_addr;             /* info@spammer.tld */
     long    data_size;                 /* message content size */
     long    rcpt_offset;               /* more recipients here */
-    long    unread_offset;             /* more unread recipients here */
     QMGR_RCPT_LIST rcpt_list;          /* complete addresses */
     int     rcpt_count;                        /* used recipient slots */
     int     rcpt_limit;                        /* maximum read in-core */
index 04c52243c6e8fa77a1cefda23aeea36570ec93da..99576001dd8007ef0e6328442f76f8c3a20d6e0e 100644 (file)
@@ -166,7 +166,6 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name,
     message->warn_time = 0;
     message->rcpt_offset = 0;
     message->verp_delims = 0;
-    message->unread_offset = 0;
     qmgr_rcpt_list_init(&message->rcpt_list);
     message->rcpt_count = 0;
     message->rcpt_limit = var_qmgr_msg_rcpt_limit;
@@ -209,7 +208,7 @@ static int qmgr_message_open(QMGR_MESSAGE *message)
     return (0);
 }
 
-/* qmgr_message_oldstyle_scan - extract required information from an old style queue file */
+/* qmgr_message_oldstyle_scan - support for Postfix < 1.0 queue files */
 
 static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
 {
@@ -241,24 +240,36 @@ static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
      * completely.
      */
     message->rcpt_unread = 0;
-    do {
+    for (;;) {
        if ((curr_offset = vstream_ftell(message->fp)) < 0)
            msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
        rec_type = rec_get(message->fp, buf, 0);
+       if (rec_type <= 0)
+           /* Report missing end record later. */
+           break;
        start = vstring_str(buf);
+       if (msg_verbose > 1)
+           msg_info("old-style scan record %c %s", rec_type, start);
+       if (rec_type == REC_TYPE_END)
+           break;
        if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_RCPT) {
            message->rcpt_unread++;
-       } else if (rec_type == REC_TYPE_MESG) {
-           if ((message->data_offset = vstream_ftell(message->fp)) < 0)
-               msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
-           if ((extra_offset = atol(start)) <= curr_offset)
-               msg_fatal("bad extra offset %s file %s",
-                         start, VSTREAM_PATH(message->fp));
-           if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
-               msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
-           message->data_size = extra_offset - message->data_offset;
+           continue;
        }
-    } while (rec_type > 0 && rec_type != REC_TYPE_END);
+       if (rec_type == REC_TYPE_MESG) {
+           if (message->data_offset == 0) {
+               if ((message->data_offset = vstream_ftell(message->fp)) < 0)
+                   msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+               if ((extra_offset = atol(start)) <= message->data_offset)
+                   msg_fatal("bad extra offset %s file %s",
+                             start, VSTREAM_PATH(message->fp));
+               if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
+                   msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+               message->data_size = extra_offset - message->data_offset;
+           }
+           continue;
+       }
+    }
 
     /*
      * Clean up.
@@ -280,7 +291,6 @@ static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
 static int qmgr_message_read(QMGR_MESSAGE *message)
 {
     VSTRING *buf;
-    long    extra_offset;
     int     rec_type;
     long    curr_offset;
     long    save_offset = message->rcpt_offset;        /* save a flag */
@@ -290,6 +300,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
     char   *name;
     char   *value;
     char   *orig_rcpt = 0;
+    int     count;
 
     /*
      * Initialize. No early returns or we have a memory leak.
@@ -311,7 +322,8 @@ 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);
+           msg_panic("%s: recipient list not empty on recipient reload",
+                     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;
@@ -330,7 +342,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
      * queue file, to protect against memory exhaustion. Recipient records
      * may appear before or after the message content, so we keep reading
      * from the queue file until we have enough recipients (rcpt_offset != 0)
-     * and until we know where the message content starts (data_offset != 0).
+     * and until we know all the non-recipient extracted segment information.
      * 
      * Note that the total recipient count record is accurate only for fresh
      * queue files. After some of the recipients are marked as done and the
@@ -339,94 +351,127 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
      * as the only impact is that the already deferred messages are not
      * chosen by qmgr_job_candidate() as often as they could.
      */
-    do {
+    for (;;) {
        if ((curr_offset = vstream_ftell(message->fp)) < 0)
            msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
        if (curr_offset == message->data_offset && curr_offset > 0) {
-           extra_offset = curr_offset + message->data_size;
-           if (extra_offset <= curr_offset)
-               msg_fatal("bad extra offset %ld file %s",
-                         extra_offset, VSTREAM_PATH(message->fp));
-           if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
+           if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0)
                msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
-           curr_offset = extra_offset;
+           curr_offset += message->data_size;
        }
        rec_type = rec_get(message->fp, buf, 0);
+       if (rec_type <= 0)
+           /* Report missing end record later. */
+           break;
        start = vstring_str(buf);
+       if (msg_verbose > 1)
+           msg_info("record %c %s", rec_type, start);
+       if (rec_type == REC_TYPE_END)
+           break;
+       if (rec_type == REC_TYPE_RCPT) {
+           /* See also below for code setting orig_rcpt. */
+           if (message->rcpt_offset == 0) {
+               message->rcpt_unread--;
+               qmgr_rcpt_list_add(&message->rcpt_list, curr_offset,
+                                  orig_rcpt ? orig_rcpt : "unknown", start);
+               if (orig_rcpt) {
+                   myfree(orig_rcpt);
+                   orig_rcpt = 0;
+               }
+               if (message->rcpt_list.len >= recipient_limit) {
+                   if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
+                       msg_fatal("vstream_ftell %s: %m",
+                                 VSTREAM_PATH(message->fp));
+                   /* Must have examined all non-recipient records. */
+                   if (curr_offset > message->data_offset)
+                       break;
+               }
+           }
+           continue;
+       }
+       if (rec_type == REC_TYPE_DONE) {
+           if (message->rcpt_offset == 0) {
+               message->rcpt_unread--;
+               if (orig_rcpt) {
+                   myfree(orig_rcpt);
+                   orig_rcpt = 0;
+               }
+           }
+           continue;
+       }
+       if (orig_rcpt != 0) {
+           /* REC_TYPE_ORCP must go before REC_TYPE_RCPT or REC_TYPE DONE. */
+           msg_warn("%s: out-of-order original recipient record <%.200s>",
+                    message->queue_id, orig_rcpt);
+           myfree(orig_rcpt);
+           orig_rcpt = 0;
+       }
+       if (rec_type == REC_TYPE_ORCP) {
+           /* See also above for code clearing orig_rcpt. */
+           if (message->rcpt_offset == 0)
+               orig_rcpt = mystrdup(start);
+           continue;
+       }
        if (rec_type == REC_TYPE_SIZE) {
-           if (message->data_size == 0) {
-               switch (sscanf(start, "%ld %ld %d", &message->data_size,
-                           &message->data_offset, &message->rcpt_unread)) {
-               case 1:
-
-                   /*
-                    * Gather data_size, data_offset and rcpt_unread values
-                    * from the old style queue file.
-                    */
+           if (message->data_offset == 0) {
+               if ((count = sscanf(start, "%ld %ld %d", &message->data_size,
+                     &message->data_offset, &message->rcpt_unread)) == 3) {
+                   /* Postfix >= 1.0 (a.k.a. 20010228). */
+                   if (message->data_offset <= 0 || message->data_size <= 0) {
+                       msg_warn("invalid size record, file %s",
+                                VSTREAM_PATH(message->fp));
+                       rec_type = REC_TYPE_ERROR;
+                       break;
+                   }
+               } else if (count == 1) {
+                   /* Postfix < 1.0 (a.k.a. 20010228). */
                    qmgr_message_oldstyle_scan(message);
-                   break;
-               case 3:
-
-                   /*
-                    * No extra work for new style queue files.
-                    */
-                   break;
-               default:
-                   msg_fatal("%s: weird size record", message->queue_id);
+               } else {
+                   /* Can't happen. */
+                   msg_warn("%s: weird size record", message->queue_id);
+                   rec_type = REC_TYPE_ERROR;
                    break;
                }
            }
-       } else if (rec_type == REC_TYPE_TIME) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_TIME) {
            if (message->arrival_time == 0)
                message->arrival_time = atol(start);
-       } else if (rec_type == REC_TYPE_FILT) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_FROM) {
+           if (message->sender == 0) {
+               message->sender = mystrdup(start);
+               opened(message->queue_id, message->sender,
+                      message->data_size, message->rcpt_unread,
+                      "queue %s", message->queue_name);
+           }
+           continue;
+       }
+       if (rec_type == REC_TYPE_FILT) {
            if (message->filter_xport != 0)
                myfree(message->filter_xport);
            message->filter_xport = mystrdup(start);
-       } else if (rec_type == REC_TYPE_INSP) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_INSP) {
            if (message->inspect_xport != 0)
                myfree(message->inspect_xport);
            message->inspect_xport = mystrdup(start);
-       } else if (rec_type == REC_TYPE_RDR) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_RDR) {
            if (message->redirect_addr != 0)
                myfree(message->redirect_addr);
            message->redirect_addr = mystrdup(start);
-       } else if (rec_type == REC_TYPE_FROM) {
-           if (message->sender == 0) {
-               message->sender = mystrdup(start);
-               opened(message->queue_id, message->sender,
-                      message->data_size, message->rcpt_unread,
-                      "queue %s", message->queue_name);
-           }
-       } else if (rec_type == REC_TYPE_DONE) {
-           if (curr_offset > message->unread_offset) {
-               message->unread_offset = curr_offset;
-               message->rcpt_unread--;
-           }
-       } else if (rec_type == REC_TYPE_RCPT) {
-           /* See also below for code setting orig_rcpt. */
-           if (message->rcpt_list.len < recipient_limit) {
-               message->rcpt_unread--;
-               qmgr_rcpt_list_add(&message->rcpt_list, curr_offset,
-                                  orig_rcpt ? orig_rcpt : "unknown", start);
-               if (orig_rcpt) {
-                   myfree(orig_rcpt);
-                   orig_rcpt = 0;
-               }
-               if (message->rcpt_list.len >= recipient_limit) {
-                   if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
-                       msg_fatal("vstream_ftell %s: %m",
-                                 VSTREAM_PATH(message->fp));
-                   if (message->data_offset != 0
-                       && message->errors_to != 0
-                       && message->return_receipt != 0)
-                       break;
-               }
-           }
-       } else if (rec_type == REC_TYPE_ATTR) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_ATTR) {
            if ((error_text = split_nameval(start, &name, &value)) != 0) {
                msg_warn("%s: bad attribute: %s: %.200s",
                         message->queue_id, error_text, start);
+               rec_type = REC_TYPE_ERROR;
                break;
            }
            /* Allow extra segment to override envelope segment info. */
@@ -439,28 +484,26 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
            else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) {
                message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value));
            }
-       } else if (rec_type == REC_TYPE_ERTO) {
-           if (message->errors_to == 0) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_ERTO) {
+           if (message->errors_to == 0)
                message->errors_to = mystrdup(start);
-               if (message->data_offset != 0
-                   && message->rcpt_offset != 0
-                   && message->return_receipt != 0)
-                   break;
-           }
-       } else if (rec_type == REC_TYPE_RRTO) {
-           if (message->return_receipt == 0) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_RRTO) {
+           if (message->return_receipt == 0)
                message->return_receipt = mystrdup(start);
-               if (message->data_offset != 0
-                   && message->rcpt_offset != 0
-                   && message->errors_to != 0)
-                   break;
-           }
-       } else if (rec_type == REC_TYPE_WARN) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_WARN) {
            if (message->warn_offset == 0) {
                message->warn_offset = curr_offset;
                message->warn_time = atol(start);
            }
-       } else if (rec_type == REC_TYPE_VERP) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_VERP) {
            if (message->verp_delims == 0) {
                if (verp_delims_verify(start) != 0) {
                    msg_warn("%s: bad VERP record content: \"%s\"",
@@ -470,26 +513,17 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
                    message->verp_delims = mystrdup(start);
                }
            }
+           continue;
        }
-       if (orig_rcpt != 0) {
-           if (rec_type != REC_TYPE_DONE)
-               msg_warn("%s: out-of-order original recipient record <%.200s>",
-                        message->queue_id, start);
-           myfree(orig_rcpt);
-           orig_rcpt = 0;
-       }
-       if (rec_type == REC_TYPE_ORCP)
-           /* See also above for code clearing orig_rcpt. */
-           if (message->rcpt_offset == 0)
-               orig_rcpt = mystrdup(start);
-    } while (rec_type > 0 && rec_type != REC_TYPE_END);
+    }
 
     /*
      * Grr.
      */
     if (orig_rcpt != 0) {
-       msg_warn("%s: out-of-order original recipient <%.200s>",
-                message->queue_id, start);
+       if (rec_type > 0)
+           msg_warn("%s: out-of-order original recipient <%.200s>",
+                    message->queue_id, orig_rcpt);
        myfree(orig_rcpt);
     }
 
@@ -497,6 +531,9 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
      * Avoid clumsiness elsewhere in the program. When sending data across an
      * IPC channel, sending an empty string is more convenient than sending a
      * null pointer.
+     * 
+     * Allow for Postfix versions that do not store return_receipt or errors_to
+     * records.
      */
     if (message->errors_to == 0)
        message->errors_to = mystrdup("");
@@ -520,19 +557,22 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
                 message->queue_id, message->rcpt_unread);
        message->rcpt_unread = 0;
     }
-    if (message->arrival_time == 0
-       || message->sender == 0
-       || message->data_offset == 0
-       || (message->rcpt_offset == 0 && rec_type != REC_TYPE_END)) {
-       msg_warn("%s: envelope records out of order", message->queue_id);
-       message->rcpt_offset = save_offset;     /* restore flag */
-       message->rcpt_unread += message->rcpt_list.len;
-       qmgr_rcpt_list_free(&message->rcpt_list);
-       qmgr_rcpt_list_init(&message->rcpt_list);
-       return (-1);
+    if (rec_type <= 0) {
+       msg_warn("%s: missing end record", message->queue_id);
+    } else if (message->arrival_time == 0) {
+       msg_warn("%s: missing arrival time record", message->queue_id);
+    } else if (message->sender == 0) {
+       msg_warn("%s: missing sender record", message->queue_id);
+    } else if (message->data_offset == 0) {
+       msg_warn("%s: missing size record", message->queue_id);
     } else {
        return (0);
     }
+    message->rcpt_offset = save_offset;                /* restore flag */
+    message->rcpt_unread += message->rcpt_list.len;
+    qmgr_rcpt_list_free(&message->rcpt_list);
+    qmgr_rcpt_list_init(&message->rcpt_list);
+    return (-1);
 }
 
 /* qmgr_message_update_warn - update the time of next delay warning */
index 28e04f2a63b38fb1f59c05d754525d4e8089da85..a7ac88035f94f40f916c56d77a7335ed7c0844ca 100644 (file)
@@ -119,7 +119,6 @@ typedef struct {
     struct stat st;                    /* queue file status */
     char   *path;                      /* name for open/remove */
     char   *sender;                    /* sender address */
-    char   *rcpt;                      /* recipient address */
 } PICKUP_INFO;
 
  /*
@@ -188,12 +187,8 @@ static int copy_segment(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info,
                         (long) info->st.st_uid, vstring_str(buf));
                continue;
            }
-       if (type == REC_TYPE_RCPT)
-           if (info->rcpt == 0)
-               info->rcpt = mystrdup(vstring_str(buf));
        if (type == REC_TYPE_TIME)
-           continue;
-       if (type == REC_TYPE_SIZE)
+           /* Use our own arrival time record instead. */
            continue;
        if (type == REC_TYPE_ATTR) {
            if ((error_text = split_nameval(vstring_str(buf), &attr_name,
@@ -217,38 +212,19 @@ static int copy_segment(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info,
            }
            continue;
        }
-       if (type == REC_TYPE_RRTO)
-           /* Use message header extracted information instead. */
-           continue;
-       if (type == REC_TYPE_ERTO)
-           /* Use message header extracted information instead. */
-           continue;
-       if (type == REC_TYPE_INSP)
-           /* Use current content inspection settings instead. */
-           continue;
 
        /*
-        * XXX Workaround: REC_TYPE_FILT (used in envelopes) == REC_TYPE_CONT
-        * (used in message content).
+        * XXX Force an empty record when the queue file content begins with
+        * whitespace, so that it won't be considered as being part of our
+        * own Received: header. What an ugly Kluge.
         */
-       if (type == REC_TYPE_FILT && *expected != REC_TYPE_CONTENT[0])
-           /* Use current content filter settings instead. */
-           continue;
-       else {
-
-           /*
-            * XXX Force an empty record when the queue file content begins
-            * with whitespace, so that it won't be considered as being part
-            * of our own Received: header. What an ugly Kluge.
-            */
-           if (check_first) {
-               check_first = 0;
-               if (VSTRING_LEN(buf) > 0 && IS_SPACE_TAB(vstring_str(buf)[0]))
-                   rec_put(cleanup, REC_TYPE_NORM, "", 0);
-           }
-           if ((REC_PUT_BUF(cleanup, type, buf)) < 0)
-               return (cleanup_service_error(info, CLEANUP_STAT_WRITE));
+       if (check_first && *expected == REC_TYPE_CONTENT[0]) {
+           check_first = 0;
+           if (VSTRING_LEN(buf) > 0 && IS_SPACE_TAB(vstring_str(buf)[0]))
+               rec_put(cleanup, REC_TYPE_NORM, "", 0);
        }
+       if ((REC_PUT_BUF(cleanup, type, buf)) < 0)
+           return (cleanup_service_error(info, CLEANUP_STAT_WRITE));
     }
     return (0);
 }
@@ -429,7 +405,6 @@ static void pickup_init(PICKUP_INFO *info)
     info->id = 0;
     info->path = 0;
     info->sender = 0;
-    info->rcpt = 0;
 }
 
 /* pickup_free - wipe info structure */
@@ -441,7 +416,6 @@ static void pickup_free(PICKUP_INFO *info)
     SAFE_FREE(info->id);
     SAFE_FREE(info->path);
     SAFE_FREE(info->sender);
-    SAFE_FREE(info->rcpt);
 }
 
 /* pickup_service - service client */
index eea77dd646b32718eb1af49871d06bf429fd4d08..29d4abdc248de0030af4a1a954a5e13674ddc530 100644 (file)
@@ -194,22 +194,94 @@ static int qmgr_message_open(QMGR_MESSAGE *message)
     return (0);
 }
 
+/* qmgr_message_oldstyle_scan - support for Postfix < 1.0 queue files */
+
+static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
+{
+    VSTRING *buf;
+    long    orig_offset,
+            curr_offset,
+            extra_offset;
+    int     rec_type;
+    char   *start;
+
+    /*
+     * Initialize. No early returns or we have a memory leak.
+     */
+    buf = vstring_alloc(100);
+    if ((orig_offset = vstream_ftell(message->fp)) < 0)
+       msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+
+    /*
+     * Rewind to the very beginning to make sure we see all records.
+     */
+    if (vstream_fseek(message->fp, 0, SEEK_SET) < 0)
+       msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+
+    /*
+     * Scan through the old style queue file. Count the total number of
+     * recipients and find the data/extra sections offsets. Note that the new
+     * queue files require that data_size equals extra_offset - data_offset,
+     * so we set data_size to this as well and ignore the size record itself
+     * completely.
+     */
+    for (;;) {
+       if ((curr_offset = vstream_ftell(message->fp)) < 0)
+           msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+       rec_type = rec_get(message->fp, buf, 0);
+       if (rec_type <= 0)
+           /* Report missing end record later. */
+           break;
+       start = vstring_str(buf);
+       if (msg_verbose > 1)
+           msg_info("old-style scan record %c %s", rec_type, start);
+       if (rec_type == REC_TYPE_END)
+           break;
+       if (rec_type == REC_TYPE_MESG) {
+           if (message->data_offset == 0) {
+               if ((message->data_offset = vstream_ftell(message->fp)) < 0)
+                   msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+               if ((extra_offset = atol(start)) <= message->data_offset)
+                   msg_fatal("bad extra offset %s file %s",
+                             start, VSTREAM_PATH(message->fp));
+               if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
+                   msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+               message->data_size = extra_offset - message->data_offset;
+           }
+           continue;
+       }
+    }
+
+    /*
+     * Clean up.
+     */
+    if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0)
+       msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+    vstring_free(buf);
+
+    /*
+     * Sanity checks. Verify that all required information was found,
+     * including the queue file end marker.
+     */
+    if (message->data_offset == 0 || rec_type != REC_TYPE_END)
+       msg_fatal("%s: envelope records out of order", message->queue_id);
+}
+
 /* qmgr_message_read - read envelope records */
 
 static int qmgr_message_read(QMGR_MESSAGE *message)
 {
     VSTRING *buf;
-    long    extra_offset;
     int     rec_type;
     long    curr_offset;
     long    save_offset = message->rcpt_offset;        /* save a flag */
     char   *start;
-    struct stat st;
     int     nrcpt = 0;
     const char *error_text;
     char   *name;
     char   *value;
     char   *orig_rcpt = 0;
+    int     count;
 
     /*
      * Initialize. No early returns or we have a memory leak.
@@ -218,7 +290,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
 
     /*
      * If we re-open this file, skip over on-file recipient records that we
-     * already looked at, and reset the in-core recipient address list.
+     * already looked at, and refill the in-core recipient address list.
      */
     if (message->rcpt_offset) {
        if (message->rcpt_list.len)
@@ -235,7 +307,7 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
      * queue file, to protect against memory exhaustion. Recipient records
      * may appear before or after the message content, so we keep reading
      * from the queue file until we have enough recipients (rcpt_offset != 0)
-     * and until we know where the message content starts (data_offset != 0).
+     * and until we know all the non-recipient extracted segment information.
      * 
      * When reading recipients from queue file, stop reading when we reach a
      * per-message in-core recipient limit rather than a global in-core
@@ -248,75 +320,123 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
      * (global recipient limit)/(active queue size limit), which gives equal
      * delay per recipient rather than equal delay per message.
      */
-    do {
+    for (;;) {
        if ((curr_offset = vstream_ftell(message->fp)) < 0)
            msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+       if (curr_offset == message->data_offset && curr_offset > 0) {
+           if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0)
+               msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+           curr_offset += message->data_size;
+       }
        rec_type = rec_get(message->fp, buf, 0);
+       if (rec_type <= 0)
+           /* Report missing end record later. */
+           break;
        start = vstring_str(buf);
-       if (rec_type == REC_TYPE_SIZE) {
-           if (message->data_size == 0)
-               sscanf(start, "%ld %ld %d",
-                      &message->data_size, &message->data_offset, &nrcpt);
-       } else if (rec_type == REC_TYPE_TIME) {
-           if (message->arrival_time == 0)
-               message->arrival_time = atol(start);
-       } else if (rec_type == REC_TYPE_FILT) {
-           if (message->filter_xport != 0)
-               myfree(message->filter_xport);
-           message->filter_xport = mystrdup(start);
-       } else if (rec_type == REC_TYPE_INSP) {
-           if (message->inspect_xport != 0)
-               myfree(message->inspect_xport);
-           message->inspect_xport = mystrdup(start);
-       } else if (rec_type == REC_TYPE_RDR) {
-           if (message->redirect_addr != 0)
-               myfree(message->redirect_addr);
-           message->redirect_addr = mystrdup(start);
-       } else if (rec_type == REC_TYPE_FROM) {
-           if (message->sender == 0) {
-               message->sender = mystrdup(start);
-               opened(message->queue_id, message->sender,
-                      message->data_size, nrcpt,
-                      "queue %s", message->queue_name);
-           }
-       } else if (rec_type == REC_TYPE_RCPT) {
+       if (msg_verbose > 1)
+           msg_info("record %c %s", rec_type, start);
+       if (rec_type == REC_TYPE_END)
+           break;
+       if (rec_type == REC_TYPE_RCPT) {
            /* See also below for code setting orig_rcpt. */
-#define FUDGE(x)       ((x) * (var_qmgr_fudge / 100.0))
-           if (message->rcpt_list.len < FUDGE(var_qmgr_rcpt_limit)) {
+           if (message->rcpt_offset == 0) {
                qmgr_rcpt_list_add(&message->rcpt_list, curr_offset,
                                   orig_rcpt ? orig_rcpt : "unknown", start);
                if (orig_rcpt) {
                    myfree(orig_rcpt);
                    orig_rcpt = 0;
                }
-               if (message->rcpt_list.len >= FUDGE(var_qmgr_rcpt_limit)) {
+               if (message->rcpt_list.len >= var_qmgr_rcpt_limit) {
                    if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0)
                        msg_fatal("vstream_ftell %s: %m",
                                  VSTREAM_PATH(message->fp));
-                   if (message->data_offset != 0
-                       && message->errors_to != 0
-                       && message->return_receipt != 0)
+                   /* Must have examined all non-recipient records. */
+                   if (curr_offset > message->data_offset)
                        break;
                }
            }
-       } else if (rec_type == REC_TYPE_MESG) {
-           if ((message->data_offset = vstream_ftell(message->fp)) < 0)
-               msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
-           if (message->rcpt_offset != 0
-               && message->errors_to != 0
-               && message->return_receipt != 0)
-               break;
-           if ((extra_offset = atol(start)) < curr_offset) {
-               msg_warn("bad extra offset %s file %s",
-                        start, VSTREAM_PATH(message->fp));
-               break;
+           continue;
+       }
+       if (rec_type == REC_TYPE_DONE) {
+           if (orig_rcpt) {
+               myfree(orig_rcpt);
+               orig_rcpt = 0;
            }
-           if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
-               msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
-       } else if (rec_type == REC_TYPE_ATTR) {
+           continue;
+       }
+       if (orig_rcpt != 0) {
+           /* REC_TYPE_ORCP must go before REC_TYPE_RCPT or REC_TYPE DONE. */
+           msg_warn("%s: out-of-order original recipient record <%.200s>",
+                    message->queue_id, orig_rcpt);
+           myfree(orig_rcpt);
+           orig_rcpt = 0;
+       }
+       if (rec_type == REC_TYPE_ORCP) {
+           /* See also above for code clearing orig_rcpt. */
+           if (message->rcpt_offset == 0)
+               orig_rcpt = mystrdup(start);
+           continue;
+       }
+       if (rec_type == REC_TYPE_SIZE) {
+           if (message->data_offset == 0) {
+               if ((count = sscanf(start, "%ld %ld %d", &message->data_size,
+                                   &message->data_offset, &nrcpt)) == 3) {
+                   /* Postfix >= 1.0 (a.k.a. 20010228). */
+                   if (message->data_offset <= 0 || message->data_size <= 0) {
+                       msg_warn("invalid size record, file %s",
+                                VSTREAM_PATH(message->fp));
+                       rec_type = REC_TYPE_ERROR;
+                       break;
+                   }
+               } else if (count == 1) {
+                   /* Postfix < 1.0 (a.k.a. 20010228). */
+                   qmgr_message_oldstyle_scan(message);
+               } else {
+                   /* Can't happen. */
+                   msg_warn("%s: weird size record", message->queue_id);
+                   rec_type = REC_TYPE_ERROR;
+                   break;
+               }
+           }
+           continue;
+       }
+       if (rec_type == REC_TYPE_TIME) {
+           if (message->arrival_time == 0)
+               message->arrival_time = atol(start);
+           continue;
+       }
+       if (rec_type == REC_TYPE_FROM) {
+           if (message->sender == 0) {
+               message->sender = mystrdup(start);
+               opened(message->queue_id, message->sender,
+                      message->data_size, nrcpt,
+                      "queue %s", message->queue_name);
+           }
+           continue;
+       }
+       if (rec_type == REC_TYPE_FILT) {
+           if (message->filter_xport != 0)
+               myfree(message->filter_xport);
+           message->filter_xport = mystrdup(start);
+           continue;
+       }
+       if (rec_type == REC_TYPE_INSP) {
+           if (message->inspect_xport != 0)
+               myfree(message->inspect_xport);
+           message->inspect_xport = mystrdup(start);
+           continue;
+       }
+       if (rec_type == REC_TYPE_RDR) {
+           if (message->redirect_addr != 0)
+               myfree(message->redirect_addr);
+           message->redirect_addr = mystrdup(start);
+           continue;
+       }
+       if (rec_type == REC_TYPE_ATTR) {
            if ((error_text = split_nameval(start, &name, &value)) != 0) {
                msg_warn("%s: bad attribute: %s: %.200s",
                         message->queue_id, error_text, start);
+               rec_type = REC_TYPE_ERROR;
                break;
            }
            /* Allow extra segment to override envelope segment info. */
@@ -329,18 +449,26 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
            else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) {
                message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value));
            }
-       } else if (rec_type == REC_TYPE_ERTO) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_ERTO) {
            if (message->errors_to == 0)
                message->errors_to = mystrdup(start);
-       } else if (rec_type == REC_TYPE_RRTO) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_RRTO) {
            if (message->return_receipt == 0)
                message->return_receipt = mystrdup(start);
-       } else if (rec_type == REC_TYPE_WARN) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_WARN) {
            if (message->warn_offset == 0) {
                message->warn_offset = curr_offset;
                message->warn_time = atol(start);
            }
-       } else if (rec_type == REC_TYPE_VERP) {
+           continue;
+       }
+       if (rec_type == REC_TYPE_VERP) {
            if (message->verp_delims == 0) {
                if (verp_delims_verify(start) != 0) {
                    msg_warn("%s: bad VERP record content: \"%s\"",
@@ -350,42 +478,27 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
                    message->verp_delims = mystrdup(start);
                }
            }
+           continue;
        }
-       if (orig_rcpt != 0) {
-           if (rec_type != REC_TYPE_DONE)
-               msg_warn("%s: out-of-order original recipient record <%.200s>",
-                        message->queue_id, start);
-           myfree(orig_rcpt);
-           orig_rcpt = 0;
-       }
-       if (rec_type == REC_TYPE_ORCP)
-           /* See also above for code clearing orig_rcpt. */
-           if (message->rcpt_offset == 0)
-               orig_rcpt = mystrdup(start);
-    } while (rec_type > 0 && rec_type != REC_TYPE_END);
+    }
 
     /*
      * Grr.
      */
     if (orig_rcpt != 0) {
-       msg_warn("%s: out-of-order original recipient <%.200s>",
-                message->queue_id, start);
+       if (rec_type > 0)
+           msg_warn("%s: out-of-order original recipient <%.200s>",
+                    message->queue_id, orig_rcpt);
        myfree(orig_rcpt);
     }
 
-    /*
-     * If there is no size record, use the queue file size instead.
-     */
-    if (message->data_size == 0) {
-       if (fstat(vstream_fileno(message->fp), &st) < 0)
-           msg_fatal("fstat %s: %m", VSTREAM_PATH(message->fp));
-       message->data_size = st.st_size;
-    }
-
     /*
      * Avoid clumsiness elsewhere in the program. When sending data across an
      * IPC channel, sending an empty string is more convenient than sending a
      * null pointer.
+     * 
+     * Allow for Postfix versions that do not store return_receipt or errors_to
+     * records.
      */
     if (message->errors_to == 0)
        message->errors_to = mystrdup("");
@@ -403,16 +516,19 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
      * Sanity checks. Verify that all required information was found,
      * including the queue file end marker.
      */
-    if (message->arrival_time == 0
-       || message->sender == 0
-       || message->data_offset == 0
-       || (message->rcpt_offset == 0 && rec_type != REC_TYPE_END)) {
-       msg_warn("%s: envelope records out of order", message->queue_id);
-       message->rcpt_offset = save_offset;     /* restore flag */
-       return (-1);
+    if (rec_type <= 0) {
+       msg_warn("%s: missing end record", message->queue_id);
+    } else if (message->arrival_time == 0) {
+       msg_warn("%s: missing arrival time record", message->queue_id);
+    } else if (message->sender == 0) {
+       msg_warn("%s: missing sender record", message->queue_id);
+    } else if (message->data_offset == 0) {
+       msg_warn("%s: missing size record", message->queue_id);
     } else {
        return (0);
     }
+    message->rcpt_offset = save_offset;                /* restore flag */
+    return (-1);
 }
 
 /* qmgr_message_update_warn - update the time of next delay warning */
@@ -543,7 +659,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
     int     len;
     int     status;
 
-#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); }
index 28cbbcd68947dfa8ca6bdd31ba38d6663c4cf17e..2e676001e6159cf9573662fe7284b68b5273c6a7 100644 (file)
@@ -482,6 +482,9 @@ static void qmqpd_send_status(QMQPD_STATE *state)
     } else if ((state->err & CLEANUP_STAT_RCPT) != 0) {
        qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
                    "Error: no recipients specified");
+    } else if ((state->err & CLEANUP_STAT_MISS_HDR) != 0) {
+       qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+                   "Error: missing message header");
     } else {
        qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
                    "Error: internal error %d", state->err);
index 8ccd4f31c8467c1b39d70290596387c241e0f420..395a849bdf611fa8668e4fd8b2f15675e7bc0a4d 100644 (file)
@@ -57,6 +57,7 @@
 #include <time.h>
 #include <string.h>
 #include <ctype.h>
+#include <stdio.h>                     /* sscanf() */
 
 /* Utility library. */
 
@@ -112,6 +113,7 @@ static void showq_report(VSTREAM *client, char *queue, char *id,
     time_t  arrival_time = 0;
     char   *start;
     long    msg_size = 0;
+    long    msg_offset = 0;
     BOUNCE_LOG *logfile;
     HTABLE *dup_filter = 0;
     char    status = (strcmp(queue, MAIL_QUEUE_ACTIVE) == 0 ? '*' :
@@ -132,8 +134,11 @@ static void showq_report(VSTREAM *client, char *queue, char *id,
            arrival_time = atol(start);
            break;
        case REC_TYPE_SIZE:
-           if ((msg_size = atol(start)) <= 0)
-               msg_size = size;
+           if (sscanf(start, "%ld %ld", &msg_size, &msg_offset) == 2) {
+               /* Postfix >= 1.0 (a.k.a. 20010228) style queue file. */
+               if (msg_size <= 0)
+                   msg_size = size;
+           }
            break;
        case REC_TYPE_FROM:
            if (*start == 0)
@@ -156,8 +161,11 @@ static void showq_report(VSTREAM *client, char *queue, char *id,
                                "", "", "", STR(printable_quoted_addr));
            break;
        case REC_TYPE_MESG:
-           if ((offset = atol(start)) > 0
-               && vstream_fseek(qfile, offset, SEEK_SET) < 0)
+           if (msg_size > 0 && msg_offset > 0
+               && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
+               msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
+           else if ((offset = atol(start)) > 0
+                    && vstream_fseek(qfile, offset, SEEK_SET) < 0)
                msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
            break;
        case REC_TYPE_END:
index 96147559d6356699b7b70bcdeb5f79dc5eaec496..ee19dfc6f73b5842a3b2f7f856c26745202029e3 100644 (file)
 #include <valid_hostname.h>
 #include <dict.h>
 #include <watchdog.h>
+#include <iostuff.h>
 
 /* Global library. */
 
@@ -1141,6 +1142,9 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
     } else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
        state->error_mask |= MAIL_ERROR_RESOURCE;
        smtpd_chat_reply(state, "451 Error: queue file write error");
+    } else if ((state->err & CLEANUP_STAT_MISS_HDR) != 0) {
+       state->error_mask |= MAIL_ERROR_POLICY;
+       smtpd_chat_reply(state, "550 Error: missing message header");
     } else {
        state->error_mask |= MAIL_ERROR_SOFTWARE;
        smtpd_chat_reply(state, "451 Error: internal error %d", state->err);
@@ -1579,6 +1583,13 @@ static void smtpd_service(VSTREAM *stream, char *unused_service, char **argv)
     smtpd_state_init(&state, stream);
     msg_info("connect from %s[%s]", state.name, state.addr);
 
+    /*
+     * XXX non_blocking() aborts upon error.
+     */
+#ifdef BROKEN_READ_SELECT_ON_BLOCKING_SOCKET
+    non_blocking(vstream_fileno(stream), NON_BLOCKING);
+#endif
+
     /*
      * See if we need to turn on verbose logging for this client.
      */
index f3ef3fe4f1cb4003c50f8c5641adf3335d1619d1..ee56be5236cafc313de3b86308075bf27808e90a 100644 (file)
 /*     Reject, defer or permit the request unconditionally. This is to be used
 /*     at the end of a restriction list in order to make the default
 /*     action explicit.
+/* .IP require_date_header
+/* .IP require_from_header
+/* .IP require_message_id_header
+/* .IP require_received_header
+/*     Reject the message when it does not contain a Date: etc. 
+/*     message header. Only the Date: header is required by mail
+/*     standards. The other headers are usually added by MTAs.
 /* .IP reject_unknown_client
 /*     Reject the request when the client hostname could not be found.
 /*     The \fIunknown_client_reject_code\fR configuration parameter
@@ -2750,6 +2757,26 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
            DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY,
                         "450 <%s>: %s rejected: defer_if_reject requested",
                             reply_name, reply_class);
+       } else if (strcasecmp(name, REQUIRE_DATE_HDR) == 0) {
+#ifndef TEST
+           rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
+                       CLEANUP_FLAG_NEED_DATE);
+#endif
+       } else if (strcasecmp(name, REQUIRE_FROM_HDR) == 0) {
+#ifndef TEST
+           rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
+                       CLEANUP_FLAG_NEED_FROM);
+#endif
+       } else if (strcasecmp(name, REQUIRE_MSGID_HDR) == 0) {
+#ifndef TEST
+           rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
+                       CLEANUP_FLAG_NEED_MSGID);
+#endif
+       } else if (strcasecmp(name, REQUIRE_RCVD_HDR) == 0) {
+#ifndef TEST
+           rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
+                       CLEANUP_FLAG_NEED_RCVD);
+#endif
        }
 
        /*
index 17120a0c93df45134811ce20ed9ce06bf8a09323..c786073a2cb25895f6885c74f0c6ab6e4d73a79a 100644 (file)
@@ -470,7 +470,7 @@ extern int initgroups(const char *, int);
 #define DBM_NO_TRAILING_NULL           /* XXX check */
 #define USE_STATVFS
 #define STATVFS_IN_SYS_STATVFS_H
-#define BROKEN_NON_BLOCKING_WRITE_SELECT
+#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE
 #endif
 
 #if defined(IRIX5)
index b0176b13a97c3a735504cdf75141e465f5526f52..90ee2c7fdac24159ada396eeab9758f3e6e30743 100644 (file)
@@ -535,23 +535,11 @@ static int vstream_fflush_some(VSTREAM *stream, int to_flush)
      * When flushing a buffer, allow for partial writes. These can happen
      * while talking to a network. Update the cached file seek position, if
      * any.
-     * 
-     * XXX Workaround for IRIX brain damage: select() indicates that a pipe is
-     * writable, but write() transfers zero bytes on non-blocking pipes. This
-     * means that there is no reasonable way to enforce write timeouts.
      */
     for (data = (char *) bp->data, len = to_flush; len > 0; len -= n, data += n) {
        if (stream->timeout)
            stream->iotime = time((time_t *) 0);
        if ((n = stream->write_fn(stream->fd, data, len, stream->timeout, stream->context)) <= 0) {
-#ifdef BROKEN_NON_BLOCKING_WRITE_SELECT
-           if (n == 0) {
-               msg_warn("%s: write() transfers 0 bytes on a writable descriptor!",
-                        myname);
-               sleep(1);
-               continue;
-           }
-#endif
            bp->flags |= VSTREAM_FLAG_ERR;
            if (errno == ETIMEDOUT)
                bp->flags |= VSTREAM_FLAG_TIMEOUT;
@@ -819,11 +807,17 @@ long    vstream_fseek(VSTREAM *stream, long offset, int whence)
      */
     switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
     case VSTREAM_FLAG_WRITE:
-       if (bp->ptr > bp->data)
+       if (bp->ptr > bp->data) {
+           if (whence == SEEK_CUR)
+               offset += (bp->ptr - bp->data); /* add unwritten data */
            if (VSTREAM_FFLUSH_SOME(stream))
                return (-1);
-       /* FALLTHROUGH */
+       }
+       VSTREAM_BUF_AT_END(bp);
+       break;
     case VSTREAM_FLAG_READ:
+       if (whence == SEEK_CUR)
+           offset += bp->cnt;                  /* subtract unread data */
     case 0:
        VSTREAM_BUF_AT_END(bp);
        break;