Home | History | Annotate | Line # | Download | only in cleanup
      1 /*	$NetBSD: cleanup_api.c,v 1.6 2026/05/09 18:49:15 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	cleanup_api 3
      6 /* SUMMARY
      7 /*	cleanup callable interface, message processing
      8 /* SYNOPSIS
      9 /*	#include "cleanup.h"
     10 /*
     11 /*	CLEANUP_STATE *cleanup_open(src)
     12 /*	VSTREAM	*src;
     13 /*
     14 /*	void	cleanup_control(state, flags)
     15 /*	CLEANUP_STATE *state;
     16 /*	int	flags;
     17 /*
     18 /*	void	CLEANUP_RECORD(state, type, buf, len)
     19 /*	CLEANUP_STATE *state;
     20 /*	int	type;
     21 /*	char	*buf;
     22 /*	int	len;
     23 /*
     24 /*	int	cleanup_flush(state)
     25 /*	CLEANUP_STATE *state;
     26 /*
     27 /*	int	cleanup_free(state)
     28 /*	CLEANUP_STATE *state;
     29 /* DESCRIPTION
     30 /*	This module implements a callable interface to the cleanup service
     31 /*	for processing one message and for writing it to queue file.
     32 /*	For a description of the cleanup service, see cleanup(8).
     33 /*
     34 /*	cleanup_open() creates a new queue file and performs other
     35 /*	per-message initialization. The result is a handle that should be
     36 /*	given to the cleanup_control(), cleanup_record(), cleanup_flush()
     37 /*	and cleanup_free() routines. The name of the queue file is in the
     38 /*	queue_id result structure member.
     39 /*
     40 /*	cleanup_control() processes per-message flags specified by the caller.
     41 /*	These flags control the handling of data errors, and must be set
     42 /*	before processing the first message record.
     43 /* .IP CLEANUP_FLAG_BOUNCE
     44 /*	The cleanup server is responsible for returning undeliverable
     45 /*	mail (too many hops, message too large) to the sender.
     46 /* .IP CLEANUP_FLAG_BCC_OK
     47 /*	It is OK to add automatic BCC recipient addresses.
     48 /* .IP CLEANUP_FLAG_FILTER
     49 /*	Enable header/body filtering. This should be enabled only with mail
     50 /*	that enters Postfix, not with locally forwarded mail or with bounce
     51 /*	messages.
     52 /* .IP CLEANUP_FLAG_MILTER
     53 /*	Enable Milter applications. This should be enabled only with mail
     54 /*	that enters Postfix, not with locally forwarded mail or with bounce
     55 /*	messages.
     56 /* .IP CLEANUP_FLAG_MAP_OK
     57 /*	Enable canonical and virtual mapping, and address masquerading.
     58 /* .PP
     59 /*	For convenience the CLEANUP_FLAG_MASK_EXTERNAL macro specifies
     60 /*	the options that are normally needed for mail that enters
     61 /*	Postfix from outside, and CLEANUP_FLAG_MASK_INTERNAL specifies
     62 /*	the options that are normally needed for internally generated or
     63 /*	forwarded mail.
     64 /*
     65 /*	CLEANUP_RECORD() is a macro that processes one message record,
     66 /*	that copies the result to the queue file, and that maintains a
     67 /*	little state machine. The last record in a valid message has type
     68 /*	REC_TYPE_END.  In order to find out if a message is corrupted,
     69 /*	the caller is encouraged to test the CLEANUP_OUT_OK(state) macro.
     70 /*	The result is false when further message processing is futile.
     71 /*	In that case, it is safe to call cleanup_flush() immediately.
     72 /*
     73 /*	cleanup_flush() closes a queue file. In case of any errors,
     74 /*	the file is removed. The result value is non-zero in case of
     75 /*	problems. In some cases a human-readable text can be found in
     76 /*	the state->reason member. In all other cases, use cleanup_strerror()
     77 /*	to translate the result into human-readable text.
     78 /*
     79 /*	cleanup_free() destroys its argument.
     80 /* .IP CLEANUP_FLAG_SMTPUTF8
     81 /*	Request SMTPUTF8 support when delivering mail.
     82 /* .IP CLEANUP_FLAG_AUTOUTF8
     83 /*	Autodetection: request SMTPUTF8 support if the message
     84 /*	contains an UTF8 message header, sender, or recipient.
     85 /* .IP CLEANUP_FLAG_REQTLS
     86 /*	The sender requested REQUIRETLS (RFC 8689) enforcement.
     87 /* DIAGNOSTICS
     88 /*	Problems and transactions are logged to \fBsyslogd\fR(8)
     89 /*	or \fBpostlogd\fR(8).
     90 /* SEE ALSO
     91 /*	cleanup(8) cleanup service description.
     92 /*	cleanup_init(8) cleanup callable interface, initialization
     93 /* LICENSE
     94 /* .ad
     95 /* .fi
     96 /*	The Secure Mailer license must be distributed with this software.
     97 /* AUTHOR(S)
     98 /*	Wietse Venema
     99 /*	IBM T.J. Watson Research
    100 /*	P.O. Box 704
    101 /*	Yorktown Heights, NY 10598, USA
    102 /*
    103 /*	Wietse Venema
    104 /*	Google, Inc.
    105 /*	111 8th Avenue
    106 /*	New York, NY 10011, USA
    107 /*
    108 /*	Wietse Venema
    109 /*	porcupine.org
    110 /*--*/
    111 
    112 /* System library. */
    113 
    114 #include <sys_defs.h>
    115 #include <errno.h>
    116 
    117 /* Utility library. */
    118 
    119 #include <msg.h>
    120 #include <vstring.h>
    121 #include <mymalloc.h>
    122 
    123 /* Global library. */
    124 
    125 #include <cleanup_user.h>
    126 #include <mail_queue.h>
    127 #include <mail_proto.h>
    128 #include <bounce.h>
    129 #include <mail_params.h>
    130 #include <mail_stream.h>
    131 #include <mail_flow.h>
    132 #include <rec_type.h>
    133 #include <smtputf8.h>
    134 
    135 /* Milter library. */
    136 
    137 #include <milter.h>
    138 
    139 /* Application-specific. */
    140 
    141 #include "cleanup.h"
    142 
    143 /* cleanup_open - open queue file and initialize */
    144 
    145 CLEANUP_STATE *cleanup_open(VSTREAM *src)
    146 {
    147     CLEANUP_STATE *state;
    148     static const char *log_queues[] = {
    149 	MAIL_QUEUE_DEFER,
    150 	MAIL_QUEUE_BOUNCE,
    151 	MAIL_QUEUE_TRACE,
    152 	0,
    153     };
    154     const char **cpp;
    155 
    156     /*
    157      * Initialize private state.
    158      */
    159     state = cleanup_state_alloc(src);
    160 
    161     /*
    162      * Open the queue file. Save the queue file name in a global variable, so
    163      * that the runtime error handler can clean up in case of problems.
    164      *
    165      * XXX For now, a lot of detail is frozen that could be more useful if it
    166      * were made configurable.
    167      */
    168     state->queue_name = mystrdup(MAIL_QUEUE_INCOMING);
    169     state->handle = mail_stream_file(state->queue_name,
    170 				   MAIL_CLASS_PUBLIC, var_queue_service, 0);
    171     state->dst = state->handle->stream;
    172     cleanup_path = mystrdup(VSTREAM_PATH(state->dst));
    173     state->queue_id = mystrdup(state->handle->id);
    174     if (msg_verbose)
    175 	msg_info("cleanup_open: open %s", cleanup_path);
    176 
    177     /*
    178      * If there is a time to get rid of spurious log files, this is it. The
    179      * down side is that this costs performance for every message, while the
    180      * probability of spurious log files is quite low.
    181      *
    182      * XXX The defer logfile is deleted when the message is moved into the
    183      * active queue. We must also remove it now, otherwise mailq produces
    184      * nonsense.
    185      */
    186     for (cpp = log_queues; *cpp; cpp++) {
    187 	if (mail_queue_remove(*cpp, state->queue_id) == 0)
    188 	    msg_warn("%s: removed spurious %s log", *cpp, state->queue_id);
    189 	else if (errno != ENOENT)
    190 	    msg_fatal("%s: remove %s log: %m", *cpp, state->queue_id);
    191     }
    192     return (state);
    193 }
    194 
    195 /* cleanup_control - process client options */
    196 
    197 void    cleanup_control(CLEANUP_STATE *state, int flags)
    198 {
    199 
    200     /*
    201      * If the client requests us to do the bouncing in case of problems,
    202      * throw away the input only in case of real show-stopper errors, such as
    203      * unrecognizable data (which should never happen) or insufficient space
    204      * for the queue file (which will happen occasionally). Otherwise,
    205      * discard input after any lethal error. See the CLEANUP_OUT_OK() macro
    206      * definition.
    207      */
    208     if (msg_verbose)
    209 	msg_info("client flags = %s", cleanup_strflags(flags));
    210     if ((state->flags = flags) & CLEANUP_FLAG_BOUNCE) {
    211 	state->err_mask = CLEANUP_STAT_MASK_INCOMPLETE;
    212     } else {
    213 	state->err_mask = ~0;
    214     }
    215 
    216     /*
    217      * Propagate requests that are specified at the envelope level. This may
    218      * be augmented later with information derived from message content.
    219      */
    220     if (state->flags & CLEANUP_FLAG_SMTPUTF8)
    221 	state->sendopts |= SMTPUTF8_FLAG_REQUESTED;
    222     if (state->flags & CLEANUP_FLAG_REQTLS)
    223 	state->sendopts |= SOPT_REQUIRETLS_ESMTP;
    224     if (msg_verbose)
    225 	msg_info("server flags = %s", cleanup_strflags(state->flags));
    226 }
    227 
    228 /* cleanup_flush - finish queue file */
    229 
    230 int     cleanup_flush(CLEANUP_STATE *state)
    231 {
    232     int     status;
    233     char   *junk;
    234     VSTRING *trace_junk;
    235 
    236     /*
    237      * Raise these errors only if we examined all queue file records.
    238      */
    239     if (CLEANUP_OUT_OK(state)) {
    240 	if (state->recip == 0)
    241 	    state->errs |= CLEANUP_STAT_RCPT;
    242 	if ((state->flags & CLEANUP_FLAG_END_SEEN) == 0)
    243 	    state->errs |= CLEANUP_STAT_BAD;
    244     }
    245 
    246     /*
    247      * Status sanitization. Always report success when the discard flag was
    248      * raised by some user-specified access rule.
    249      */
    250     if (state->flags & CLEANUP_FLAG_DISCARD)
    251 	state->errs = 0;
    252 
    253     /*
    254      * Apply external mail filter.
    255      *
    256      * XXX Include test for a built-in action to tempfail this message.
    257      */
    258     if (CLEANUP_MILTER_OK(state)) {
    259 	if (state->milters)
    260 	    cleanup_milter_inspect(state, state->milters);
    261 	else if (cleanup_milters) {
    262 	    cleanup_milter_emul_data(state, cleanup_milters);
    263 	    if (CLEANUP_MILTER_OK(state))
    264 		cleanup_milter_inspect(state, cleanup_milters);
    265 	}
    266     }
    267 
    268     /*
    269      * Update the preliminary message size and count fields with the actual
    270      * values.
    271      */
    272     if (CLEANUP_OUT_OK(state))
    273 	cleanup_final(state);
    274 
    275     /*
    276      * If there was an error that requires us to generate a bounce message
    277      * (mail submitted with the Postfix sendmail command, mail forwarded by
    278      * the local(8) delivery agent, or mail re-queued with "postsuper -r"),
    279      * send a bounce notification, reset the error flags in case of success,
    280      * and request deletion of the incoming queue file and of the optional
    281      * DSN SUCCESS records from virtual alias expansion.
    282      *
    283      * XXX It would make no sense to knowingly report success after we already
    284      * have bounced all recipients, especially because the information in the
    285      * DSN SUCCESS notice is completely redundant compared to the information
    286      * in the bounce notice (however, both may be incomplete when the queue
    287      * file size would exceed the safety limit).
    288      *
    289      * An alternative is to keep the DSN SUCCESS records and to delegate bounce
    290      * notification to the queue manager, just like we already delegate
    291      * success notification. This requires that we leave the undeliverable
    292      * message in the incoming queue; versions up to 20050726 did exactly
    293      * that. Unfortunately, this broke with over-size queue files, because
    294      * the queue manager cannot handle incomplete queue files (and it should
    295      * not try to do so).
    296      */
    297 #define CAN_BOUNCE() \
    298 	((state->errs & CLEANUP_STAT_MASK_CANT_BOUNCE) == 0 \
    299 	    && state->sender != 0 \
    300 	    && (state->flags & CLEANUP_FLAG_BOUNCE) != 0)
    301 
    302     if (state->errs != 0 && CAN_BOUNCE())
    303 	cleanup_bounce(state);
    304 
    305     /*
    306      * Optionally, place the message on hold, but only if the message was
    307      * received successfully and only if it's not being discarded for other
    308      * reasons. This involves renaming the queue file before "finishing" it
    309      * (or else the queue manager would grab it too early) and updating our
    310      * own idea of the queue file name for error recovery and for error
    311      * reporting purposes.
    312      *
    313      * XXX Include test for a built-in action to tempfail this message.
    314      */
    315     if (state->errs == 0 && (state->flags & CLEANUP_FLAG_DISCARD) == 0) {
    316 	if ((state->flags & CLEANUP_FLAG_HOLD) != 0
    317 #ifdef DELAY_ACTION
    318 	    || state->defer_delay > 0
    319 #endif
    320 	    ) {
    321 	    myfree(state->queue_name);
    322 #ifdef DELAY_ACTION
    323 	    state->queue_name = mystrdup((state->flags & CLEANUP_FLAG_HOLD) ?
    324 				     MAIL_QUEUE_HOLD : MAIL_QUEUE_DEFERRED);
    325 #else
    326 	    state->queue_name = mystrdup(MAIL_QUEUE_HOLD);
    327 #endif
    328 	    mail_stream_ctl(state->handle,
    329 			    CA_MAIL_STREAM_CTL_QUEUE(state->queue_name),
    330 			    CA_MAIL_STREAM_CTL_CLASS((char *) 0),
    331 			    CA_MAIL_STREAM_CTL_SERVICE((char *) 0),
    332 #ifdef DELAY_ACTION
    333 			    CA_MAIL_STREAM_CTL_DELAY(state->defer_delay),
    334 #endif
    335 			    CA_MAIL_STREAM_CTL_END);
    336 	    junk = cleanup_path;
    337 	    cleanup_path = mystrdup(VSTREAM_PATH(state->handle->stream));
    338 	    myfree(junk);
    339 
    340 	    /*
    341 	     * XXX: When delivering to a non-incoming queue, do not consume
    342 	     * in_flow tokens. Unfortunately we can't move the code that
    343 	     * consumes tokens until after the mail is received, because that
    344 	     * would increase the risk of duplicate deliveries (RFC 1047).
    345 	     */
    346 	    (void) mail_flow_put(1);
    347 	}
    348 	state->errs = mail_stream_finish(state->handle, (VSTRING *) 0);
    349     } else {
    350 
    351 	/*
    352 	 * XXX: When discarding mail, should we consume in_flow tokens? See
    353 	 * also the comments above for mail that is placed on hold.
    354 	 */
    355 #if 0
    356 	(void) mail_flow_put(1);
    357 #endif
    358 	mail_stream_cleanup(state->handle);
    359     }
    360     state->handle = 0;
    361     state->dst = 0;
    362 
    363     /*
    364      * If there was an error, or if the message must be discarded for other
    365      * reasons, remove the queue file and the optional trace file with DSN
    366      * SUCCESS records from virtual alias expansion.
    367      */
    368     if (state->errs != 0 || (state->flags & CLEANUP_FLAG_DISCARD) != 0) {
    369 	if (cleanup_trace_path)
    370 	    (void) REMOVE(vstring_str(cleanup_trace_path));
    371 	if (REMOVE(cleanup_path))
    372 	    msg_warn("remove %s: %m", cleanup_path);
    373 	msg_info("%s: removed (%s)", state->queue_id, state->errs ?
    374 		 "canceled" : "discarded");
    375     }
    376 
    377     /*
    378      * Make sure that our queue file will not be deleted by the error handler
    379      * AFTER we have taken responsibility for delivery. Better to deliver
    380      * twice than to lose mail.
    381      */
    382     trace_junk = cleanup_trace_path;
    383     cleanup_trace_path = 0;			/* don't delete upon error */
    384     junk = cleanup_path;
    385     cleanup_path = 0;				/* don't delete upon error */
    386 
    387     if (trace_junk)
    388 	vstring_free(trace_junk);
    389     myfree(junk);
    390 
    391     /*
    392      * Cleanup internal state. This is simply complementary to the
    393      * initializations at the beginning of cleanup_open().
    394      */
    395     if (msg_verbose)
    396 	msg_info("cleanup_flush: status %d", state->errs);
    397     status = state->errs;
    398     return (status);
    399 }
    400 
    401 /* cleanup_free - pay the last respects */
    402 
    403 void    cleanup_free(CLEANUP_STATE *state)
    404 {
    405 
    406     /*
    407      * Emulate disconnect event. CLEANUP_FLAG_MILTER may be turned off after
    408      * we have started.
    409      */
    410     if (cleanup_milters != 0 && state->milters == 0)
    411 	milter_disc_event(cleanup_milters);
    412     cleanup_state_free(state);
    413 }
    414