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
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.
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
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
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
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
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"
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.
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 */
/*
* 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.
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;
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);
}
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;
}
#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,
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.
{
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)
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;
}
/*
* 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);
(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);
}
}
}
+/* 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)
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.
*/
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>",
*/
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));
}
*/
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",
* 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;
}
}
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);
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;
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",
#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.
#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 */
/*
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.
*/
* 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
*
* 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
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 */
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 */
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;
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)
{
* 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.
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 *name;
char *value;
char *orig_rcpt = 0;
+ int count;
/*
* Initialize. No early returns or we have a memory leak.
*/
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;
* 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
* 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. */
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\"",
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);
}
* 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("");
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 */
struct stat st; /* queue file status */
char *path; /* name for open/remove */
char *sender; /* sender address */
- char *rcpt; /* recipient address */
} PICKUP_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,
}
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);
}
info->id = 0;
info->path = 0;
info->sender = 0;
- info->rcpt = 0;
}
/* pickup_free - wipe info structure */
SAFE_FREE(info->id);
SAFE_FREE(info->path);
SAFE_FREE(info->sender);
- SAFE_FREE(info->rcpt);
}
/* pickup_service - service client */
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.
/*
* 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)
* 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
* (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. */
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\"",
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("");
* 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 */
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); }
} 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);
#include <time.h>
#include <string.h>
#include <ctype.h>
+#include <stdio.h> /* sscanf() */
/* Utility library. */
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 ? '*' :
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)
"", "", "", 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:
#include <valid_hostname.h>
#include <dict.h>
#include <watchdog.h>
+#include <iostuff.h>
/* Global library. */
} 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);
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.
*/
/* 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
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
}
/*
#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)
* 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;
*/
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;