/*++
/* NAME
/*	cleanup_envelope 3
/* SUMMARY
/*	process envelope segment
/* SYNOPSIS
/*	#include <cleanup.h>
/*
/*	void	cleanup_envelope(state, type, buf, len)
/*	CLEANUP_STATE *state;
/*	int	type;
/*	const char *buf;
/*	ssize_t	len;
/* DESCRIPTION
/*	This module processes envelope records and writes the result
/*	to the queue file.  It validates the message structure, rewrites
/*	sender/recipient addresses to canonical form, and expands recipients
/*	according to entries in the virtual table. This routine absorbs but
/*	does not emit the envelope to content boundary record.
/*
/*	Arguments:
/* .IP state
/*	Queue file and message processing state. This state is updated
/*	as records are processed and as errors happen.
/* .IP type
/*	Record type.
/* .IP buf
/*	Record content.
/* .IP len
/*	Record content length.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*
/*	Wietse Venema
/*	porcupine.org
/*--*/

/* System library. */

#include <sys_defs.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>			/* ssscanf() */
#include <ctype.h>

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <mymalloc.h>
#include <stringops.h>
#include <nvtable.h>

/* Global library. */

#include <record.h>
#include <rec_type.h>
#include <cleanup_user.h>
#include <qmgr_user.h>
#include <mail_params.h>
#include <verp_sender.h>
#include <mail_proto.h>
#include <dsn_mask.h>
#include <rec_attr_map.h>
#include <smtputf8.h>
#include <deliver_request.h>

/* Application-specific. */

#include "cleanup.h"

#define STR	vstring_str
#define STREQ(x,y) (strcmp((x), (y)) == 0)

static void cleanup_envelope_process(CLEANUP_STATE *, int, const char *, ssize_t);

/* cleanup_envelope - initialize message envelope */

void    cleanup_envelope(CLEANUP_STATE *state, int type,
			         const char *str, ssize_t len)
{

    /*
     * 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, data size
     * first, for backwards compatibility reasons.
     */
    cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
		       (REC_TYPE_SIZE_CAST1) 0,	/* extra offs - content offs */
		       (REC_TYPE_SIZE_CAST2) 0,	/* content offset */
		       (REC_TYPE_SIZE_CAST3) 0,	/* recipient count */
		       (REC_TYPE_SIZE_CAST4) 0,	/* qmgr options */
		       (REC_TYPE_SIZE_CAST5) 0,	/* content length */
		       (REC_TYPE_SIZE_CAST6) 0);	/* smtputf8 */

    /*
     * Pass control to the actual envelope processing routine.
     */
    state->action = cleanup_envelope_process;
    cleanup_envelope_process(state, type, str, len);
}

/* cleanup_envelope_process - process one envelope record */

static void cleanup_envelope_process(CLEANUP_STATE *state, int type,
				             const char *buf, ssize_t len)
{
    const char *myname = "cleanup_envelope_process";
    char   *attr_name;
    char   *attr_value;
    const char *error_text;
    int     extra_opts;
    int     junk;
    int     mapped_type = type;
    const char *mapped_buf = buf;
    int     milter_count;

#ifdef DELAY_ACTION
    int     defer_delay;

#endif

    if (msg_verbose)
	msg_info("initial 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

    /*
     * XXX We instantiate a MILTERS structure even when the filter count is
     * zero (for example, all filters are in ACCEPT state, or the SMTP server
     * sends a dummy MILTERS structure without any filters), otherwise the
     * cleanup server would apply the non_smtpd_milters setting
     * inappropriately.
     */
    if (type == REC_TYPE_MILT_COUNT) {
	/* Not part of queue file format. */
	if ((milter_count = atoi(buf)) >= 0)
	    cleanup_milter_receive(state, milter_count);
	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) {
	    mapped_buf = attr_value;
	    mapped_type = junk;
	}
    }

    /*
     * Sanity check.
     */
    if (strchr(REC_TYPE_ENVELOPE, type) == 0) {
	msg_warn("%s: message rejected: unexpected record type %d in envelope",
		 state->queue_id, type);
	state->errs |= CLEANUP_STAT_BAD;
	return;
    }

    /*
     * Although recipient records appear at the end of the initial or
     * extracted envelope, the code for processing recipient records is first
     * because there can be lots of them.
     * 
     * Recipient records may be mixed with other information (such as FILTER or
     * REDIRECT actions from SMTPD). In that case the queue manager needs to
     * examine all queue file records before it can start delivery. This is
     * not a problem when SMTPD recipient lists are small.
     * 
     * However, if recipient records are not mixed with other records
     * (typically, mailing list mail) then we can make an optimization: the
     * queue manager does not need to examine every envelope record before it
     * can start deliveries. This can help with very large mailing lists.
     */

    /*
     * On the transition from non-recipient records to recipient records,
     * emit some records and do some sanity checks.
     * 
     * XXX Moving the envelope sender (and the test for its presence) to the
     * extracted segment can reduce qmqpd memory requirements because it no
     * longer needs to read the entire message into main memory.
     */
    if ((state->flags & CLEANUP_FLAG_INRCPT) == 0
	&& strchr(REC_TYPE_ENV_RECIPIENT, type) != 0) {
	if (state->sender == 0) {
	    msg_warn("%s: message rejected: missing sender envelope record",
		     state->queue_id);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}
	if (state->arrival_time.tv_sec == 0) {
	    msg_warn("%s: message rejected: missing time envelope record",
		     state->queue_id);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}

	/*
	 * XXX This works by accident, because the sender is recorded at the
	 * beginning of the envelope segment.
	 */
	if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0
	    && state->sender && *state->sender
	    && var_delay_warn_time > 0) {
	    cleanup_out_format(state, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
			       REC_TYPE_WARN_ARG(state->arrival_time.tv_sec
						 + var_delay_warn_time));
	}
	state->flags |= CLEANUP_FLAG_INRCPT;
    }

    /*
     * Initial 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 (mapped_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(mapped_buf);
	return;
    }
    if (mapped_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(mapped_buf) || (junk = atoi(mapped_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, state->orig_rcpt);
	    myfree(state->orig_rcpt);
	}
	state->orig_rcpt = mystrdup(buf);
	return;
    }
    if (type == REC_TYPE_MESG) {
	state->action = cleanup_message;
	if (state->flags & CLEANUP_FLAG_INRCPT) {
	    if (state->milters || cleanup_milters) {
		/* Make room to append recipient. */
		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;
	}
	return;
    }

    /*
     * Initial envelope non-recipient record processing.
     * 
     * If this message was requeued with "postsuper -r", use their sender
     * options flags, excluding flags derived from headers or envelopes.
     * Those flags may be derived again, depending on Postfix configuration.
     */
    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;
    if (type == REC_TYPE_SIZE) {
	/* Ignore their SIZE record, but keep the non-derived sender options. */
	(void) sscanf(buf, "%*s %*s %*s %*s %*s %d", &state->sendopts);
	state->sendopts &= ~SOPT_FLAG_DERIVED;
	return;
    }
    if (mapped_type == REC_TYPE_CTIME)
	/* Use our own expiration time base record instead. */
	return;
    if (type == REC_TYPE_TIME) {
	/* First instance wins. */
	if (state->arrival_time.tv_sec == 0) {
	    REC_TYPE_TIME_SCAN(buf, state->arrival_time);
	    cleanup_out(state, type, buf, len);
	}
	/* Generate our own expiration time base record. */
	cleanup_out_format(state, REC_TYPE_ATTR, "%s=%ld",
			   MAIL_ATTR_CREATE_TIME, (long) time((time_t *) 0));
	return;
    }
    if (type == REC_TYPE_FULL) {
	/* First instance wins. */
	if (state->fullname == 0) {
	    state->fullname = mystrdup(buf);
	    cleanup_out(state, type, buf, len);
	}
	return;
    }
    if (type == REC_TYPE_FROM) {
	off_t   after_sender_offs;

	/* Allow only one instance. */
	if (state->sender != 0) {
	    msg_warn("%s: message rejected: multiple envelope sender records",
		     state->queue_id);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}
	if (state->milters || cleanup_milters) {
	    /* Remember the sender record offset. */
	    if ((state->sender_pt_offset = vstream_ftell(state->dst)) < 0)
		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
	}
	after_sender_offs = cleanup_addr_sender(state, buf);
	if (state->milters || cleanup_milters) {
	    /* Remember the after-sender record offset. */
	    state->sender_pt_target = after_sender_offs;
	}
	if (cleanup_milters != 0
	    && state->milters == 0
	    && CLEANUP_MILTER_OK(state))
	    cleanup_milter_emul_mail(state, cleanup_milters, state->sender);
	return;
    }
    if (mapped_type == REC_TYPE_DSN_ENVID) {
	/* Don't break "postsuper -r" after Milter overrides ENVID. */
	if (!allprint(mapped_buf)) {
	    msg_warn("%s: message rejected: bad DSN envelope ID record",
		     state->queue_id);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}
	if (state->dsn_envid != 0)
	    myfree(state->dsn_envid);
	state->dsn_envid = mystrdup(mapped_buf);
	cleanup_out(state, type, buf, len);
	return;
    }
    if (mapped_type == REC_TYPE_DSN_RET) {
	/* Don't break "postsuper -r" after Milter overrides RET. */
	if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0
	    || DSN_RET_OK(junk) == 0) {
	    msg_warn("%s: message rejected: bad DSN RET record <%.200s>",
		     state->queue_id, buf);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}
	state->dsn_ret = junk;
	cleanup_out(state, type, buf, len);
	return;
    }
    if (type == REC_TYPE_WARN) {
	/* First instance wins. */
	if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0) {
	    state->flags |= CLEANUP_FLAG_WARN_SEEN;
	    cleanup_out(state, type, buf, len);
	}
	return;
    }
    /* XXX Needed for cleanup_bounce(); sanity check usage. */
    if (type == REC_TYPE_VERP) {
	if (state->verp_delims == 0) {
	    if (state->sender == 0 || state->sender[0] == 0) {
		msg_warn("%s: ignoring VERP request for null sender",
			 state->queue_id);
	    } else if (verp_delims_verify(buf) != 0) {
		msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
			 state->queue_id, buf);
	    } else {
		state->verp_delims = mystrdup(buf);
		cleanup_out(state, type, buf, len);
	    }
	}
	return;
    }
    if (type == REC_TYPE_ATTR) {
	if (state->attr->used >= var_qattr_count_limit) {
	    msg_warn("%s: message rejected: attribute count exceeds limit %d",
		     state->queue_id, var_qattr_count_limit);
	    state->errs |= CLEANUP_STAT_BAD;
	    return;
	}
	if (strcmp(attr_name, MAIL_ATTR_RWR_CONTEXT) == 0) {
	    /* Choose header rewriting context. See also cleanup_addr.c. */
	    if (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL)) {
		state->hdr_rewrite_context = MAIL_ATTR_RWR_LOCAL;
	    } else if (STREQ(attr_value, MAIL_ATTR_RWR_REMOTE)) {
		state->hdr_rewrite_context =
		    (*var_remote_rwr_domain ? MAIL_ATTR_RWR_REMOTE : 0);
	    } else {
		msg_warn("%s: message rejected: bad rewriting context: %.100s",
			 state->queue_id, attr_value);
		state->errs |= CLEANUP_STAT_BAD;
		return;
	    }
	}
	if (strcmp(attr_name, MAIL_ATTR_TRACE_FLAGS) == 0) {
	    if (!alldig(attr_value)) {
		msg_warn("%s: message rejected: bad TFLAG record <%.200s>",
			 state->queue_id, buf);
		state->errs |= CLEANUP_STAT_BAD;
		return;
	    }
	    if (state->tflags == 0)
		state->tflags = DEL_REQ_TRACE_FLAGS(atoi(attr_value));
	}
	nvtable_update(state->attr, attr_name, attr_value);
	cleanup_out(state, type, buf, len);
	return;
    } else {
	cleanup_out(state, type, buf, len);
	return;
    }
}
