diff options
Diffstat (limited to 'src/cleanup/cleanup_api.c')
-rw-r--r-- | src/cleanup/cleanup_api.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/src/cleanup/cleanup_api.c b/src/cleanup/cleanup_api.c new file mode 100644 index 0000000..4fc5e2e --- /dev/null +++ b/src/cleanup/cleanup_api.c @@ -0,0 +1,395 @@ +/*++ +/* NAME +/* cleanup_api 3 +/* SUMMARY +/* cleanup callable interface, message processing +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* CLEANUP_STATE *cleanup_open(src) +/* VSTREAM *src; +/* +/* void cleanup_control(state, flags) +/* CLEANUP_STATE *state; +/* int flags; +/* +/* void CLEANUP_RECORD(state, type, buf, len) +/* CLEANUP_STATE *state; +/* int type; +/* char *buf; +/* int len; +/* +/* int cleanup_flush(state) +/* CLEANUP_STATE *state; +/* +/* int cleanup_free(state) +/* CLEANUP_STATE *state; +/* DESCRIPTION +/* This module implements a callable interface to the cleanup service +/* for processing one message and for writing it to queue file. +/* For a description of the cleanup service, see cleanup(8). +/* +/* cleanup_open() creates a new queue file and performs other +/* per-message initialization. The result is a handle that should be +/* given to the cleanup_control(), cleanup_record(), cleanup_flush() +/* and cleanup_free() routines. The name of the queue file is in the +/* queue_id result structure member. +/* +/* cleanup_control() processes per-message flags specified by the caller. +/* These flags control the handling of data errors, and must be set +/* before processing the first message record. +/* .IP CLEANUP_FLAG_BOUNCE +/* The cleanup server is responsible for returning undeliverable +/* mail (too many hops, message too large) to the sender. +/* .IP CLEANUP_FLAG_BCC_OK +/* It is OK to add automatic BCC recipient addresses. +/* .IP CLEANUP_FLAG_FILTER +/* Enable header/body filtering. This should be enabled only with mail +/* that enters Postfix, not with locally forwarded mail or with bounce +/* messages. +/* .IP CLEANUP_FLAG_MILTER +/* Enable Milter applications. This should be enabled only with mail +/* that enters Postfix, not with locally forwarded mail or with bounce +/* messages. +/* .IP CLEANUP_FLAG_MAP_OK +/* Enable canonical and virtual mapping, and address masquerading. +/* .PP +/* For convenience the CLEANUP_FLAG_MASK_EXTERNAL macro specifies +/* the options that are normally needed for mail that enters +/* Postfix from outside, and CLEANUP_FLAG_MASK_INTERNAL specifies +/* the options that are normally needed for internally generated or +/* forwarded mail. +/* +/* CLEANUP_RECORD() is a macro that processes one message record, +/* that copies the result to the queue file, and that maintains a +/* little state machine. The last record in a valid message has type +/* REC_TYPE_END. In order to find out if a message is corrupted, +/* the caller is encouraged to test the CLEANUP_OUT_OK(state) macro. +/* The result is false when further message processing is futile. +/* In that case, it is safe to call cleanup_flush() immediately. +/* +/* cleanup_flush() closes a queue file. In case of any errors, +/* the file is removed. The result value is non-zero in case of +/* problems. In some cases a human-readable text can be found in +/* the state->reason member. In all other cases, use cleanup_strerror() +/* to translate the result into human-readable text. +/* +/* cleanup_free() destroys its argument. +/* .IP CLEANUP_FLAG_SMTPUTF8 +/* Request SMTPUTF8 support when delivering mail. +/* .IP CLEANUP_FLAG_AUTOUTF8 +/* Autodetection: request SMTPUTF8 support if the message +/* contains an UTF8 message header, sender, or recipient. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* cleanup(8) cleanup service description. +/* cleanup_init(8) cleanup callable interface, initialization +/* 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 +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <cleanup_user.h> +#include <mail_queue.h> +#include <mail_proto.h> +#include <bounce.h> +#include <mail_params.h> +#include <mail_stream.h> +#include <mail_flow.h> +#include <rec_type.h> +#include <smtputf8.h> + +/* Milter library. */ + +#include <milter.h> + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_open - open queue file and initialize */ + +CLEANUP_STATE *cleanup_open(VSTREAM *src) +{ + CLEANUP_STATE *state; + static const char *log_queues[] = { + MAIL_QUEUE_DEFER, + MAIL_QUEUE_BOUNCE, + MAIL_QUEUE_TRACE, + 0, + }; + const char **cpp; + + /* + * Initialize private state. + */ + state = cleanup_state_alloc(src); + + /* + * Open the queue file. Save the queue file name in a global variable, so + * that the runtime error handler can clean up in case of problems. + * + * XXX For now, a lot of detail is frozen that could be more useful if it + * were made configurable. + */ + state->queue_name = mystrdup(MAIL_QUEUE_INCOMING); + state->handle = mail_stream_file(state->queue_name, + MAIL_CLASS_PUBLIC, var_queue_service, 0); + state->dst = state->handle->stream; + cleanup_path = mystrdup(VSTREAM_PATH(state->dst)); + state->queue_id = mystrdup(state->handle->id); + if (msg_verbose) + msg_info("cleanup_open: open %s", cleanup_path); + + /* + * If there is a time to get rid of spurious log files, this is it. The + * down side is that this costs performance for every message, while the + * probability of spurious log files is quite low. + * + * XXX The defer logfile is deleted when the message is moved into the + * active queue. We must also remove it now, otherwise mailq produces + * nonsense. + */ + for (cpp = log_queues; *cpp; cpp++) { + if (mail_queue_remove(*cpp, state->queue_id) == 0) + msg_warn("%s: removed spurious %s log", *cpp, state->queue_id); + else if (errno != ENOENT) + msg_fatal("%s: remove %s log: %m", *cpp, state->queue_id); + } + return (state); +} + +/* cleanup_control - process client options */ + +void cleanup_control(CLEANUP_STATE *state, int flags) +{ + + /* + * If the client requests us to do the bouncing in case of problems, + * throw away the input only in case of real show-stopper errors, such as + * unrecognizable data (which should never happen) or insufficient space + * for the queue file (which will happen occasionally). Otherwise, + * discard input after any lethal error. See the CLEANUP_OUT_OK() macro + * definition. + */ + if (msg_verbose) + msg_info("cleanup flags = %s", cleanup_strflags(flags)); + if ((state->flags = flags) & CLEANUP_FLAG_BOUNCE) { + state->err_mask = CLEANUP_STAT_MASK_INCOMPLETE; + } else { + state->err_mask = ~0; + } + if (state->flags & CLEANUP_FLAG_SMTPUTF8) + state->smtputf8 = SMTPUTF8_FLAG_REQUESTED; +} + +/* cleanup_flush - finish queue file */ + +int cleanup_flush(CLEANUP_STATE *state) +{ + int status; + char *junk; + VSTRING *trace_junk; + + /* + * Raise these errors only if we examined all queue file records. + */ + if (CLEANUP_OUT_OK(state)) { + if (state->recip == 0) + state->errs |= CLEANUP_STAT_RCPT; + if ((state->flags & CLEANUP_FLAG_END_SEEN) == 0) + state->errs |= CLEANUP_STAT_BAD; + } + + /* + * Status sanitization. Always report success when the discard flag was + * raised by some user-specified access rule. + */ + if (state->flags & CLEANUP_FLAG_DISCARD) + state->errs = 0; + + /* + * Apply external mail filter. + * + * XXX Include test for a built-in action to tempfail this message. + */ + if (CLEANUP_MILTER_OK(state)) { + if (state->milters) + cleanup_milter_inspect(state, state->milters); + else if (cleanup_milters) { + cleanup_milter_emul_data(state, cleanup_milters); + if (CLEANUP_MILTER_OK(state)) + cleanup_milter_inspect(state, cleanup_milters); + } + } + + /* + * Update the preliminary message size and count fields with the actual + * values. + */ + if (CLEANUP_OUT_OK(state)) + cleanup_final(state); + + /* + * If there was an error that requires us to generate a bounce message + * (mail submitted with the Postfix sendmail command, mail forwarded by + * the local(8) delivery agent, or mail re-queued with "postsuper -r"), + * send a bounce notification, reset the error flags in case of success, + * and request deletion of the the incoming queue file and of the + * optional DSN SUCCESS records from virtual alias expansion. + * + * XXX It would make no sense to knowingly report success after we already + * have bounced all recipients, especially because the information in the + * DSN SUCCESS notice is completely redundant compared to the information + * in the bounce notice (however, both may be incomplete when the queue + * file size would exceed the safety limit). + * + * An alternative is to keep the DSN SUCCESS records and to delegate bounce + * notification to the queue manager, just like we already delegate + * success notification. This requires that we leave the undeliverable + * message in the incoming queue; versions up to 20050726 did exactly + * that. Unfortunately, this broke with over-size queue files, because + * the queue manager cannot handle incomplete queue files (and it should + * not try to do so). + */ +#define CAN_BOUNCE() \ + ((state->errs & CLEANUP_STAT_MASK_CANT_BOUNCE) == 0 \ + && state->sender != 0 \ + && (state->flags & CLEANUP_FLAG_BOUNCE) != 0) + + if (state->errs != 0 && CAN_BOUNCE()) + cleanup_bounce(state); + + /* + * Optionally, place the message on hold, but only if the message was + * received successfully and only if it's not being discarded for other + * reasons. This involves renaming the queue file before "finishing" it + * (or else the queue manager would grab it too early) and updating our + * own idea of the queue file name for error recovery and for error + * reporting purposes. + * + * XXX Include test for a built-in action to tempfail this message. + */ + if (state->errs == 0 && (state->flags & CLEANUP_FLAG_DISCARD) == 0) { + if ((state->flags & CLEANUP_FLAG_HOLD) != 0 +#ifdef DELAY_ACTION + || state->defer_delay > 0 +#endif + ) { + myfree(state->queue_name); +#ifdef DELAY_ACTION + state->queue_name = mystrdup((state->flags & CLEANUP_FLAG_HOLD) ? + MAIL_QUEUE_HOLD : MAIL_QUEUE_DEFERRED); +#else + state->queue_name = mystrdup(MAIL_QUEUE_HOLD); +#endif + mail_stream_ctl(state->handle, + CA_MAIL_STREAM_CTL_QUEUE(state->queue_name), + CA_MAIL_STREAM_CTL_CLASS((char *) 0), + CA_MAIL_STREAM_CTL_SERVICE((char *) 0), +#ifdef DELAY_ACTION + CA_MAIL_STREAM_CTL_DELAY(state->defer_delay), +#endif + CA_MAIL_STREAM_CTL_END); + junk = cleanup_path; + cleanup_path = mystrdup(VSTREAM_PATH(state->handle->stream)); + myfree(junk); + + /* + * XXX: When delivering to a non-incoming queue, do not consume + * in_flow tokens. Unfortunately we can't move the code that + * consumes tokens until after the mail is received, because that + * would increase the risk of duplicate deliveries (RFC 1047). + */ + (void) mail_flow_put(1); + } + state->errs = mail_stream_finish(state->handle, (VSTRING *) 0); + } else { + + /* + * XXX: When discarding mail, should we consume in_flow tokens? See + * also the comments above for mail that is placed on hold. + */ +#if 0 + (void) mail_flow_put(1); +#endif + mail_stream_cleanup(state->handle); + } + state->handle = 0; + state->dst = 0; + + /* + * If there was an error, or if the message must be discarded for other + * reasons, remove the queue file and the optional trace file with DSN + * SUCCESS records from virtual alias expansion. + */ + if (state->errs != 0 || (state->flags & CLEANUP_FLAG_DISCARD) != 0) { + if (cleanup_trace_path) + (void) REMOVE(vstring_str(cleanup_trace_path)); + if (REMOVE(cleanup_path)) + msg_warn("remove %s: %m", cleanup_path); + } + + /* + * Make sure that our queue file will not be deleted by the error handler + * AFTER we have taken responsibility for delivery. Better to deliver + * twice than to lose mail. + */ + trace_junk = cleanup_trace_path; + cleanup_trace_path = 0; /* don't delete upon error */ + junk = cleanup_path; + cleanup_path = 0; /* don't delete upon error */ + + if (trace_junk) + vstring_free(trace_junk); + myfree(junk); + + /* + * Cleanup internal state. This is simply complementary to the + * initializations at the beginning of cleanup_open(). + */ + if (msg_verbose) + msg_info("cleanup_flush: status %d", state->errs); + status = state->errs; + return (status); +} + +/* cleanup_free - pay the last respects */ + +void cleanup_free(CLEANUP_STATE *state) +{ + + /* + * Emulate disconnect event. CLEANUP_FLAG_MILTER may be turned off after + * we have started. + */ + if (cleanup_milters != 0 && state->milters == 0) + milter_disc_event(cleanup_milters); + cleanup_state_free(state); +} |