示例#1
0
void    cleanup_extracted_process(CLEANUP_STATE *state, int type,
				          const char *buf, ssize_t len)
{
    const char *myname = "cleanup_extracted_process";
    const char *encoding;
    char   *attr_name;
    char   *attr_value;
    const char *error_text;
    int     extra_opts;
    int     junk;

#ifdef DELAY_ACTION
    int     defer_delay;

#endif

    if (msg_verbose)
	msg_info("extracted envelope %c %.*s", type, (int) len, buf);

    if (type == REC_TYPE_FLGS) {
	/* Not part of queue file format. */
	extra_opts = atoi(buf);
	if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA)
	    msg_warn("%s: ignoring bad extra flags: 0x%x",
		     state->queue_id, extra_opts);
	else
	    state->flags |= extra_opts;
	return;
    }
#ifdef DELAY_ACTION
    if (type == REC_TYPE_DELAY) {
	/* Not part of queue file format. */
	defer_delay = atoi(buf);
	if (defer_delay <= 0)
	    msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf);
	else
	    state->defer_delay = defer_delay;
	return;
    }
#endif

    if (strchr(REC_TYPE_EXTRACT, type) == 0) {
	msg_warn("%s: message rejected: "
		 "unexpected record type %d in extracted envelope",
		 state->queue_id, type);
	state->errs |= CLEANUP_STAT_BAD;
	return;
    }

    /*
     * Map DSN attribute name to pseudo record type so that we don't have to
     * pollute the queue file with records that are incompatible with past
     * Postfix versions. Preferably, people should be able to back out from
     * an upgrade without losing mail.
     */
    if (type == REC_TYPE_ATTR) {
	vstring_strcpy(state->attr_buf, buf);
	error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value);
	if (error_text != 0) {
	    msg_warn("%s: message rejected: malformed attribute: %s: %.100s",
		     state->queue_id, error_text, buf);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}
	/* Zero-length values are place holders for unavailable values. */
	if (*attr_value == 0) {
	    msg_warn("%s: spurious null attribute value for \"%s\" -- ignored",
		     state->queue_id, attr_name);
	    return;
	}
	if ((junk = rec_attr_map(attr_name)) != 0) {
	    buf = attr_value;
	    type = junk;
	}
    }

    /*
     * On the transition from non-recipient records to recipient records,
     * emit optional information from header/body content.
     */
    if ((state->flags & CLEANUP_FLAG_INRCPT) == 0
	&& strchr(REC_TYPE_EXT_RECIPIENT, type) != 0) {
	if (state->filter != 0)
	    cleanup_out_string(state, REC_TYPE_FILT, state->filter);
	if (state->redirect != 0)
	    cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
	if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) != 0)
	    cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s",
			       MAIL_ATTR_ENCODING, encoding);
	state->flags |= CLEANUP_FLAG_INRCPT;
	/* Make room to append more meta records. */
	if (state->milters || cleanup_milters) {
	    if ((state->append_meta_pt_offset = vstream_ftell(state->dst)) < 0)
		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
	    cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
	    if ((state->append_meta_pt_target = vstream_ftell(state->dst)) < 0)
		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
	}
    }

    /*
     * Extracted envelope recipient record processing.
     */
    if (type == REC_TYPE_RCPT) {
	if (state->sender == 0) {		/* protect showq */
	    msg_warn("%s: message rejected: envelope recipient precedes sender",
		     state->queue_id);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}
	if (state->orig_rcpt == 0)
	    state->orig_rcpt = mystrdup(buf);
	cleanup_addr_recipient(state, buf);
	if (cleanup_milters != 0
	    && state->milters == 0
	    && CLEANUP_MILTER_OK(state))
	    cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip);
	myfree(state->orig_rcpt);
	state->orig_rcpt = 0;
	if (state->dsn_orcpt != 0) {
	    myfree(state->dsn_orcpt);
	    state->dsn_orcpt = 0;
	}
	state->dsn_notify = 0;
	return;
    }
    if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) {
	if (state->orig_rcpt != 0) {
	    myfree(state->orig_rcpt);
	    state->orig_rcpt = 0;
	}
	if (state->dsn_orcpt != 0) {
	    myfree(state->dsn_orcpt);
	    state->dsn_orcpt = 0;
	}
	state->dsn_notify = 0;
	return;
    }
    if (type == REC_TYPE_DSN_ORCPT) {
	if (state->dsn_orcpt) {
	    msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>",
		     state->queue_id, state->dsn_orcpt);
	    myfree(state->dsn_orcpt);
	}
	state->dsn_orcpt = mystrdup(buf);
	return;
    }
    if (type == REC_TYPE_DSN_NOTIFY) {
	if (state->dsn_notify) {
	    msg_warn("%s: ignoring out-of-order DSN notify record <%d>",
		     state->queue_id, state->dsn_notify);
	    state->dsn_notify = 0;
	}
	if (!alldig(buf) || (junk = atoi(buf)) == 0 || DSN_NOTIFY_OK(junk) == 0)
	    msg_warn("%s: ignoring malformed dsn notify record <%.200s>",
		     state->queue_id, buf);
	else
	    state->qmgr_opts |=
		QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk);
	return;
    }
    if (type == REC_TYPE_ORCP) {
	if (state->orig_rcpt != 0) {
	    msg_warn("%s: ignoring out-of-order original recipient record <%.200s>",
		     state->queue_id, buf);
	    myfree(state->orig_rcpt);
	}
	state->orig_rcpt = mystrdup(buf);
	return;
    }
    if (type == REC_TYPE_END) {
	/* Make room to append recipient. */
	if ((state->milters || cleanup_milters)
	    && state->append_rcpt_pt_offset < 0) {
	    if ((state->append_rcpt_pt_offset = vstream_ftell(state->dst)) < 0)
		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
	    cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
	    if ((state->append_rcpt_pt_target = vstream_ftell(state->dst)) < 0)
		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
	}
	state->flags &= ~CLEANUP_FLAG_INRCPT;
	state->flags |= CLEANUP_FLAG_END_SEEN;
	cleanup_extracted_finish(state);
	return;
    }

    /*
     * Extracted envelope non-recipient record processing.
     */
    if (state->flags & CLEANUP_FLAG_INRCPT)
	/* Tell qmgr that recipient records are mixed with other information. */
	state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER;
    cleanup_out(state, type, buf, len);
    return;
}
示例#2
0
int     cleanup_bounce(CLEANUP_STATE *state)
{
    const char *myname = "cleanup_bounce";
    VSTRING *buf = vstring_alloc(100);
    const CLEANUP_STAT_DETAIL *detail;
    DSN_SPLIT dp;
    const char *dsn_status;
    const char *dsn_text;
    char   *rcpt = 0;
    RECIPIENT recipient;
    DSN     dsn;
    char   *attr_name;
    char   *attr_value;
    char   *dsn_orcpt = 0;
    int     dsn_notify = 0;
    char   *orig_rcpt = 0;
    char   *start;
    int     rec_type;
    int     junk;
    long    curr_offset;
    const char *encoding;
    const char *dsn_envid;
    int     dsn_ret;
    int     bounce_err;

    /*
     * Parse the failure reason if one was given, otherwise use a generic
     * mapping from cleanup-internal error code to (DSN + text).
     */
    if (state->reason) {
	dsn_split(&dp, "5.0.0", state->reason);
	dsn_status = DSN_STATUS(dp.dsn);
	dsn_text = dp.text;
    } else {
	detail = cleanup_stat_detail(state->errs);
	dsn_status = detail->dsn;
	dsn_text = detail->text;
    }

    /*
     * Create a bounce logfile with one entry for each final recipient.
     * Degrade gracefully in case of no recipients or no queue file.
     * 
     * Victor Duchovni observes that the number of recipients in the queue file
     * can potentially be very large due to virtual alias expansion. This can
     * expand the recipient count by virtual_alias_expansion_limit (default:
     * 1000) times.
     * 
     * After a queue file write error, purge any unwritten data (so that
     * vstream_fseek() won't fail while trying to flush it) and reset the
     * stream error flags to avoid false alarms.
     */
    if (vstream_ferror(state->dst) || vstream_fflush(state->dst)) {
	(void) vstream_fpurge(state->dst, VSTREAM_PURGE_BOTH);
	vstream_clearerr(state->dst);
    }
    if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
	msg_fatal("%s: seek %s: %m", myname, cleanup_path);

    while ((state->errs & CLEANUP_STAT_WRITE) == 0) {
	if ((curr_offset = vstream_ftell(state->dst)) < 0)
	    msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
	if ((rec_type = rec_get(state->dst, buf, 0)) <= 0
	    || rec_type == REC_TYPE_END)
	    break;
	start = STR(buf);
	if (rec_type == REC_TYPE_ATTR) {
	    if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
		|| *attr_value == 0)
		continue;
	    /* Map attribute names to pseudo record type. */
	    if ((junk = rec_attr_map(attr_name)) != 0) {
		start = attr_value;
		rec_type = junk;
	    }
	}
	switch (rec_type) {
	case REC_TYPE_DSN_ORCPT:		/* RCPT TO ORCPT parameter */
	    if (dsn_orcpt != 0)			/* can't happen */
		myfree(dsn_orcpt);
	    dsn_orcpt = mystrdup(start);
	    break;
	case REC_TYPE_DSN_NOTIFY:		/* RCPT TO NOTIFY parameter */
	    if (alldig(start) && (junk = atoi(start)) > 0
		&& DSN_NOTIFY_OK(junk))
		dsn_notify = junk;
	    else
		dsn_notify = 0;
	    break;
	case REC_TYPE_ORCP:			/* unmodified RCPT TO address */
	    if (orig_rcpt != 0)			/* can't happen */
		myfree(orig_rcpt);
	    orig_rcpt = mystrdup(start);
	    break;
	case REC_TYPE_RCPT:			/* rewritten RCPT TO address */
	    rcpt = start;
	    RECIPIENT_ASSIGN(&recipient, curr_offset,
			     dsn_orcpt ? dsn_orcpt : "", dsn_notify,
			     orig_rcpt ? orig_rcpt : rcpt, rcpt);
	    (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
	    cleanup_bounce_append(state, &recipient, &dsn);
	    /* FALLTHROUGH */
	case REC_TYPE_DRCP:			/* canceled recipient */
	case REC_TYPE_DONE:			/* can't happen */
	    if (orig_rcpt != 0) {
		myfree(orig_rcpt);
		orig_rcpt = 0;
	    }
	    if (dsn_orcpt != 0) {
		myfree(dsn_orcpt);
		dsn_orcpt = 0;
	    }
	    dsn_notify = 0;
	    break;
	}
    }
    if (orig_rcpt != 0)				/* can't happen */
	myfree(orig_rcpt);
    if (dsn_orcpt != 0)				/* can't happen */
	myfree(dsn_orcpt);

    /*
     * No recipients. Yes, this can happen.
     */
    if ((state->errs & CLEANUP_STAT_WRITE) == 0 && rcpt == 0) {
	RECIPIENT_ASSIGN(&recipient, 0, "", 0, "", "unknown");
	(void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
	cleanup_bounce_append(state, &recipient, &dsn);
    }
    vstring_free(buf);

    /*
     * Flush the bounce logfile to the sender. See also qmgr_active.c.
     */
    if ((state->errs & CLEANUP_STAT_WRITE) == 0) {
	if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) == 0)
	    encoding = MAIL_ATTR_ENC_NONE;
	dsn_envid = state->dsn_envid ?
	    state->dsn_envid : "";
	/* Do not send unfiltered (body) content. */
	dsn_ret = (state->errs & (CLEANUP_STAT_CONT | CLEANUP_STAT_SIZE)) ?
	    DSN_RET_HDRS : state->dsn_ret;

	if (state->verp_delims == 0 || var_verp_bounce_off) {
	    bounce_err =
		bounce_flush(BOUNCE_FLAG_CLEAN,
			     state->queue_name, state->queue_id,
			     encoding, state->sender, dsn_envid,
			     dsn_ret);
	} else {
	    bounce_err =
		bounce_flush_verp(BOUNCE_FLAG_CLEAN,
				  state->queue_name, state->queue_id,
				  encoding, state->sender, dsn_envid,
				  dsn_ret, state->verp_delims);
	}
	if (bounce_err != 0) {
	    msg_warn("%s: bounce message failure", state->queue_id);
	    state->errs |= CLEANUP_STAT_WRITE;
	}
    }

    /*
     * Schedule this message (and trace logfile) for deletion when all is
     * well. When all is not well these files would be deleted too, but the
     * client would get a different completion status so we have to carefully
     * maintain the bits anyway.
     */
    if ((state->errs &= CLEANUP_STAT_WRITE) == 0)
	state->flags |= CLEANUP_FLAG_DISCARD;

    return (state->errs);
}
示例#3
0
static int qmgr_message_read(QMGR_MESSAGE *message)
{
    VSTRING *buf;
    int     rec_type;
    long    curr_offset;
    long    save_offset = message->rcpt_offset;	/* save a flag */
    int     save_unread = message->rcpt_unread;	/* save a count */
    char   *start;
    int     recipient_limit;
    const char *error_text;
    char   *name;
    char   *value;
    char   *orig_rcpt = 0;
    int     count;
    int     dsn_notify = 0;
    char   *dsn_orcpt = 0;
    int     n;
    int     have_log_client_attr = 0;

    /*
     * Initialize. No early returns or we have a memory leak.
     */
    buf = vstring_alloc(100);

    /*
     * If we re-open this file, skip over on-file recipient records that we
     * already looked at, and refill the in-core recipient address list.
     * 
     * For the first time, the message recipient limit is calculated from the
     * global recipient limit. This is to avoid reading little recipients
     * when the active queue is near empty. When the queue becomes full, only
     * the necessary amount is read in core. Such priming is necessary
     * because there are no message jobs yet.
     * 
     * For the next time, the recipient limit is based solely on the message
     * jobs' positions in the job lists and/or job stacks.
     */
    if (message->rcpt_offset) {
	if (message->rcpt_list.len)
	    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;
	recipient_limit = message->rcpt_limit - message->rcpt_count;
    } else {
	recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count;
	if (recipient_limit < message->rcpt_limit)
	    recipient_limit = message->rcpt_limit;
    }
    /* Keep interrupt latency in check. */
    if (recipient_limit > 5000)
	recipient_limit = 5000;
    if (recipient_limit <= 0)
	msg_panic("%s: no recipient slots available", message->queue_id);
    if (msg_verbose)
	msg_info("%s: recipient limit %d", message->queue_id, recipient_limit);

    /*
     * Read envelope records. XXX Rely on the front-end programs to enforce
     * record size limits. Read up to recipient_limit recipients from the
     * queue file, to protect against memory exhaustion. Recipient records
     * may appear before or after the message content, so we keep reading
     * from the queue file until we have enough recipients (rcpt_offset != 0)
     * and until we know all the non-recipient information.
     * 
     * Note that the total recipient count record is accurate only for fresh
     * queue files. After some of the recipients are marked as done and the
     * queue file is deferred, it can be used as upper bound estimate only.
     * Fortunately, this poses no major problem on the scheduling algorithm,
     * as the only impact is that the already deferred messages are not
     * chosen by qmgr_job_candidate() as often as they could.
     * 
     * On the first open, we must examine all non-recipient records.
     * 
     * Optimization: when we know that recipient records are not mixed with
     * non-recipient records, as is typical with mailing list mail, then we
     * can avoid having to examine all the queue file records before we can
     * start deliveries. This avoids some file system thrashing with huge
     * mailing lists.
     */
    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_raw(message->fp, buf, 0, REC_FLAG_NONE);
	start = vstring_str(buf);
	if (msg_verbose > 1)
	    msg_info("record %c %s", rec_type, start);
	if (rec_type == REC_TYPE_PTR) {
	    if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR)
		break;
	    /* Need to update curr_offset after pointer jump. */
	    continue;
	}
	if (rec_type <= 0) {
	    msg_warn("%s: message rejected: missing end record",
		     message->queue_id);
	    break;
	}
	if (rec_type == REC_TYPE_END) {
	    message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
	    break;
	}

	/*
	 * Map named attributes to pseudo record types, so that we don't have
	 * to pollute the queue file with records that are incompatible with
	 * past Postfix versions. Preferably, people should be able to back
	 * out from an upgrade without losing mail.
	 */
	if (rec_type == REC_TYPE_ATTR) {
	    if ((error_text = split_nameval(start, &name, &value)) != 0) {
		msg_warn("%s: ignoring bad attribute: %s: %.200s",
			 message->queue_id, error_text, start);
		rec_type = REC_TYPE_ERROR;
		break;
	    }
	    if ((n = rec_attr_map(name)) != 0) {
		start = value;
		rec_type = n;
	    }
	}

	/*
	 * Process recipient records.
	 */
	if (rec_type == REC_TYPE_RCPT) {
	    /* See also below for code setting orig_rcpt etc. */
	    if (message->rcpt_offset == 0) {
		message->rcpt_unread--;
		recipient_list_add(&message->rcpt_list, curr_offset,
				   dsn_orcpt ? dsn_orcpt : "",
				   dsn_notify ? dsn_notify : 0,
				   orig_rcpt ? orig_rcpt : "", start);
		if (dsn_orcpt) {
		    myfree(dsn_orcpt);
		    dsn_orcpt = 0;
		}
		if (orig_rcpt) {
		    myfree(orig_rcpt);
		    orig_rcpt = 0;
		}
		if (dsn_notify)
		    dsn_notify = 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->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
			/* We already examined all non-recipient records. */
			break;
		    if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER)
			/* Examine all remaining non-recipient records. */
			continue;
		    /* Optimizations for "pure recipient" record sections. */
		    if (curr_offset > message->data_offset) {
			/* We already examined all non-recipient records. */
			message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT;
			break;
		    }
		    /* Examine non-recipient records in extracted segment. */
		    if (vstream_fseek(message->fp, message->data_offset
				      + message->data_size, SEEK_SET) < 0)
			msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
		    continue;
		}
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) {
	    if (message->rcpt_offset == 0) {
		message->rcpt_unread--;
		if (dsn_orcpt) {
		    myfree(dsn_orcpt);
		    dsn_orcpt = 0;
		}
		if (orig_rcpt) {
		    myfree(orig_rcpt);
		    orig_rcpt = 0;
		}
		if (dsn_notify)
		    dsn_notify = 0;
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_DSN_ORCPT) {
	    /* See also above for code clearing dsn_orcpt. */
	    if (dsn_orcpt != 0) {
		msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>",
			 message->queue_id, dsn_orcpt);
		myfree(dsn_orcpt);
		dsn_orcpt = 0;
	    }
	    if (message->rcpt_offset == 0)
		dsn_orcpt = mystrdup(start);
	    continue;
	}
	if (rec_type == REC_TYPE_DSN_NOTIFY) {
	    /* See also above for code clearing dsn_notify. */
	    if (dsn_notify != 0) {
		msg_warn("%s: ignoring out-of-order DSN notify flags <%d>",
			 message->queue_id, dsn_notify);
		dsn_notify = 0;
	    }
	    if (message->rcpt_offset == 0) {
		if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n))
		    msg_warn("%s: ignoring malformed DSN notify flags <%.200s>",
			     message->queue_id, start);
		else
		    dsn_notify = n;
		continue;
	    }
	}
	if (rec_type == REC_TYPE_ORCP) {
	    /* See also above for code clearing orig_rcpt. */
	    if (orig_rcpt != 0) {
		msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
			 message->queue_id, orig_rcpt);
		myfree(orig_rcpt);
		orig_rcpt = 0;
	    }
	    if (message->rcpt_offset == 0)
		orig_rcpt = mystrdup(start);
	    continue;
	}

	/*
	 * Process non-recipient records.
	 */
	if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT)
	    /* We already examined all non-recipient records. */
	    continue;
	if (rec_type == REC_TYPE_SIZE) {
	    if (message->data_offset == 0) {
		if ((count = sscanf(start, "%ld %ld %d %d %ld %d",
				 &message->data_size, &message->data_offset,
				    &message->rcpt_unread, &message->rflags,
				    &message->cont_length,
				    &message->smtputf8)) >= 3) {
		    /* Postfix >= 1.0 (a.k.a. 20010228). */
		    if (message->data_offset <= 0 || message->data_size <= 0) {
			msg_warn("%s: invalid size record: %.100s",
				 message->queue_id, start);
			rec_type = REC_TYPE_ERROR;
			break;
		    }
		    if (message->rflags & ~QMGR_READ_FLAG_USER) {
			msg_warn("%s: invalid flags in size record: %.100s",
				 message->queue_id, start);
			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: message rejected: weird size record",
			     message->queue_id);
		    rec_type = REC_TYPE_ERROR;
		    break;
		}
	    }
	    /* Postfix < 2.4 compatibility. */
	    if (message->cont_length == 0) {
		message->cont_length = message->data_size;
	    } else if (message->cont_length < 0) {
		msg_warn("%s: invalid size record: %.100s",
			 message->queue_id, start);
		rec_type = REC_TYPE_ERROR;
		break;
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_TIME) {
	    if (message->arrival_time.tv_sec == 0)
		REC_TYPE_TIME_SCAN(start, message->arrival_time);
	    continue;
	}
	if (rec_type == REC_TYPE_CTIME) {
	    if (message->create_time == 0)
		message->create_time = atol(start);
	    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_FROM) {
	    if (message->sender == 0) {
		message->sender = mystrdup(start);
		opened(message->queue_id, message->sender,
		       message->cont_length, message->rcpt_unread,
		       "queue %s", message->queue_name);
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_DSN_ENVID) {
	    if (message->dsn_envid == 0)
		message->dsn_envid = mystrdup(start);
	}
	if (rec_type == REC_TYPE_DSN_RET) {
	    if (message->dsn_ret == 0) {
		if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n))
		    msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s",
			     message->queue_id, start);
		else
		    message->dsn_ret = n;
	    }
	}
	if (rec_type == REC_TYPE_ATTR) {
	    /* Allow extra segment to override envelope segment info. */
	    if (strcmp(name, MAIL_ATTR_ENCODING) == 0) {
		if (message->encoding != 0)
		    myfree(message->encoding);
		message->encoding = mystrdup(value);
	    }

	    /*
	     * Backwards compatibility. Before Postfix 2.3, the logging
	     * attributes were called client_name, etc. Now they are called
	     * log_client_name. etc., and client_name is used for the actual
	     * client information. To support old queue files we accept both
	     * names for the purpose of logging; the new name overrides the
	     * old one.
	     * 
	     * XXX Do not use the "legacy" client_name etc. attribute values for
	     * initializing the logging attributes, when this file already
	     * contains the "modern" log_client_name etc. logging attributes.
	     * Otherwise, logging attributes that are not present in the
	     * queue file would be set with information from the real client.
	     */
	    else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) {
		if (have_log_client_attr == 0 && message->client_name == 0)
		    message->client_name = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) {
		if (have_log_client_attr == 0 && message->client_addr == 0)
		    message->client_addr = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) {
		if (have_log_client_attr == 0 && message->client_port == 0)
		    message->client_port = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) {
		if (have_log_client_attr == 0 && message->client_proto == 0)
		    message->client_proto = mystrdup(value);
	    } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) {
		if (have_log_client_attr == 0 && message->client_helo == 0)
		    message->client_helo = mystrdup(value);
	    }
	    /* Original client attributes. */
	    else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) {
		if (message->client_name != 0)
		    myfree(message->client_name);
		message->client_name = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) {
		if (message->client_addr != 0)
		    myfree(message->client_addr);
		message->client_addr = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) {
		if (message->client_port != 0)
		    myfree(message->client_port);
		message->client_port = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) {
		if (message->client_proto != 0)
		    myfree(message->client_proto);
		message->client_proto = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) {
		if (message->client_helo != 0)
		    myfree(message->client_helo);
		message->client_helo = mystrdup(value);
		have_log_client_attr = 1;
	    } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) {
		if (message->sasl_method == 0)
		    message->sasl_method = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			   message->queue_id, MAIL_ATTR_SASL_METHOD, value);
	    } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) {
		if (message->sasl_username == 0)
		    message->sasl_username = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			 message->queue_id, MAIL_ATTR_SASL_USERNAME, value);
	    } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) {
		if (message->sasl_sender == 0)
		    message->sasl_sender = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			   message->queue_id, MAIL_ATTR_SASL_SENDER, value);
	    } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) {
		if (message->log_ident == 0)
		    message->log_ident = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			     message->queue_id, MAIL_ATTR_LOG_IDENT, value);
	    } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) {
		if (message->rewrite_context == 0)
		    message->rewrite_context = mystrdup(value);
		else
		    msg_warn("%s: ignoring multiple %s attribute: %s",
			   message->queue_id, MAIL_ATTR_RWR_CONTEXT, value);
	    }

	    /*
	     * Optional tracing flags (verify, sendmail -v, sendmail -bv).
	     * This record is killed after a trace logfile report is sent and
	     * after the logfile is deleted.
	     */
	    else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) {
		message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value));
		if (message->tflags == DEL_REQ_FLAG_RECORD)
		    message->tflags_offset = curr_offset;
		else
		    message->tflags_offset = 0;
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_WARN) {
	    if (message->warn_offset == 0) {
		message->warn_offset = curr_offset;
		REC_TYPE_WARN_SCAN(start, message->warn_time);
	    }
	    continue;
	}
	if (rec_type == REC_TYPE_VERP) {
	    if (message->verp_delims == 0) {
		if (message->sender == 0 || message->sender[0] == 0) {
		    msg_warn("%s: ignoring VERP request for null sender",
			     message->queue_id);
		} else if (verp_delims_verify(start) != 0) {
		    msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
			     message->queue_id, start);
		} else {
		    if (msg_verbose)
			msg_info("%s: enabling VERP for sender \"%.100s\"",
				 message->queue_id, message->sender);
		    message->single_rcpt = 1;
		    message->verp_delims = mystrdup(start);
		}
	    }
	    continue;
	}
    }

    /*
     * Grr.
     */
    if (dsn_orcpt != 0) {
	if (rec_type > 0)
	    msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>",
		     message->queue_id, dsn_orcpt);
	myfree(dsn_orcpt);
    }
    if (orig_rcpt != 0) {
	if (rec_type > 0)
	    msg_warn("%s: ignoring out-of-order original recipient <%.200s>",
		     message->queue_id, orig_rcpt);
	myfree(orig_rcpt);
    }

    /*
     * After sending a "delayed" warning, request sender notification when
     * message delivery is completed. While "mail delayed" notifications are
     * bad enough because they multiply the amount of email traffic, "delay
     * cleared" notifications are even worse because they come in a sudden
     * burst when the queue drains after a network outage.
     */
    if (var_dsn_delay_cleared && message->warn_time < 0)
	message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT;

    /*
     * Remember when we have read the last recipient batch. Note that we do
     * it here after reading as reading might have used considerable amount
     * of time.
     */
    message->refill_time = sane_time();

    /*
     * 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.
     */
    if (message->dsn_envid == 0)
	message->dsn_envid = mystrdup("");
    if (message->encoding == 0)
	message->encoding = mystrdup(MAIL_ATTR_ENC_NONE);
    if (message->client_name == 0)
	message->client_name = mystrdup("");
    if (message->client_addr == 0)
	message->client_addr = mystrdup("");
    if (message->client_port == 0)
	message->client_port = mystrdup("");
    if (message->client_proto == 0)
	message->client_proto = mystrdup("");
    if (message->client_helo == 0)
	message->client_helo = mystrdup("");
    if (message->sasl_method == 0)
	message->sasl_method = mystrdup("");
    if (message->sasl_username == 0)
	message->sasl_username = mystrdup("");
    if (message->sasl_sender == 0)
	message->sasl_sender = mystrdup("");
    if (message->log_ident == 0)
	message->log_ident = mystrdup("");
    if (message->rewrite_context == 0)
	message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL);
    /* Postfix < 2.3 compatibility. */
    if (message->create_time == 0)
	message->create_time = message->arrival_time.tv_sec;

    /*
     * Clean up.
     */
    vstring_free(buf);

    /*
     * Sanity checks. Verify that all required information was found,
     * including the queue file end marker.
     */
    if (message->rcpt_unread < 0
	|| (message->rcpt_offset == 0 && message->rcpt_unread != 0)) {
	msg_warn("%s: rcpt count mismatch (%d)",
		 message->queue_id, message->rcpt_unread);
	message->rcpt_unread = 0;
    }
    if (rec_type <= 0) {
	/* Already logged warning. */
    } else if (message->arrival_time.tv_sec == 0) {
	msg_warn("%s: message rejected: missing arrival time record",
		 message->queue_id);
    } else if (message->sender == 0) {
	msg_warn("%s: message rejected: missing sender record",
		 message->queue_id);
    } else if (message->data_offset == 0) {
	msg_warn("%s: message rejected: missing size record",
		 message->queue_id);
    } else {
	return (0);
    }
    message->rcpt_offset = save_offset;		/* restore flag */
    message->rcpt_unread = save_unread;		/* restore count */
    recipient_list_free(&message->rcpt_list);
    recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE);
    return (-1);
}