1 /* $NetBSD: cleanup_api.c,v 1.5 2025/02/25 19:15:44 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 /* DIAGNOSTICS
86 /* Problems and transactions are logged to \fBsyslogd\fR(8)
87 /* or \fBpostlogd\fR(8).
88 /* SEE ALSO
89 /* cleanup(8) cleanup service description.
90 /* cleanup_init(8) cleanup callable interface, initialization
91 /* LICENSE
92 /* .ad
93 /* .fi
94 /* The Secure Mailer license must be distributed with this software.
95 /* AUTHOR(S)
96 /* Wietse Venema
97 /* IBM T.J. Watson Research
98 /* P.O. Box 704
99 /* Yorktown Heights, NY 10598, USA
100 /*
101 /* Wietse Venema
102 /* Google, Inc.
103 /* 111 8th Avenue
104 /* New York, NY 10011, USA
105 /*
106 /* Wietse Venema
107 /* porcupine.org
108 /*--*/
109
110 /* System library. */
111
112 #include <sys_defs.h>
113 #include <errno.h>
114
115 /* Utility library. */
116
117 #include <msg.h>
118 #include <vstring.h>
119 #include <mymalloc.h>
120
121 /* Global library. */
122
123 #include <cleanup_user.h>
124 #include <mail_queue.h>
125 #include <mail_proto.h>
126 #include <bounce.h>
127 #include <mail_params.h>
128 #include <mail_stream.h>
129 #include <mail_flow.h>
130 #include <rec_type.h>
131 #include <smtputf8.h>
132
133 /* Milter library. */
134
135 #include <milter.h>
136
137 /* Application-specific. */
138
139 #include "cleanup.h"
140
141 /* cleanup_open - open queue file and initialize */
142
cleanup_open(VSTREAM * src)143 CLEANUP_STATE *cleanup_open(VSTREAM *src)
144 {
145 CLEANUP_STATE *state;
146 static const char *log_queues[] = {
147 MAIL_QUEUE_DEFER,
148 MAIL_QUEUE_BOUNCE,
149 MAIL_QUEUE_TRACE,
150 0,
151 };
152 const char **cpp;
153
154 /*
155 * Initialize private state.
156 */
157 state = cleanup_state_alloc(src);
158
159 /*
160 * Open the queue file. Save the queue file name in a global variable, so
161 * that the runtime error handler can clean up in case of problems.
162 *
163 * XXX For now, a lot of detail is frozen that could be more useful if it
164 * were made configurable.
165 */
166 state->queue_name = mystrdup(MAIL_QUEUE_INCOMING);
167 state->handle = mail_stream_file(state->queue_name,
168 MAIL_CLASS_PUBLIC, var_queue_service, 0);
169 state->dst = state->handle->stream;
170 cleanup_path = mystrdup(VSTREAM_PATH(state->dst));
171 state->queue_id = mystrdup(state->handle->id);
172 if (msg_verbose)
173 msg_info("cleanup_open: open %s", cleanup_path);
174
175 /*
176 * If there is a time to get rid of spurious log files, this is it. The
177 * down side is that this costs performance for every message, while the
178 * probability of spurious log files is quite low.
179 *
180 * XXX The defer logfile is deleted when the message is moved into the
181 * active queue. We must also remove it now, otherwise mailq produces
182 * nonsense.
183 */
184 for (cpp = log_queues; *cpp; cpp++) {
185 if (mail_queue_remove(*cpp, state->queue_id) == 0)
186 msg_warn("%s: removed spurious %s log", *cpp, state->queue_id);
187 else if (errno != ENOENT)
188 msg_fatal("%s: remove %s log: %m", *cpp, state->queue_id);
189 }
190 return (state);
191 }
192
193 /* cleanup_control - process client options */
194
cleanup_control(CLEANUP_STATE * state,int flags)195 void cleanup_control(CLEANUP_STATE *state, int flags)
196 {
197
198 /*
199 * If the client requests us to do the bouncing in case of problems,
200 * throw away the input only in case of real show-stopper errors, such as
201 * unrecognizable data (which should never happen) or insufficient space
202 * for the queue file (which will happen occasionally). Otherwise,
203 * discard input after any lethal error. See the CLEANUP_OUT_OK() macro
204 * definition.
205 */
206 if (msg_verbose)
207 msg_info("client flags = %s", cleanup_strflags(flags));
208 if ((state->flags = flags) & CLEANUP_FLAG_BOUNCE) {
209 state->err_mask = CLEANUP_STAT_MASK_INCOMPLETE;
210 } else {
211 state->err_mask = ~0;
212 }
213 if (state->flags & CLEANUP_FLAG_SMTPUTF8)
214 state->sendopts |= SMTPUTF8_FLAG_REQUESTED;
215 /* TODO(wietse) REQUIRETLS. */
216 if (msg_verbose)
217 msg_info("server flags = %s", cleanup_strflags(state->flags));
218 }
219
220 /* cleanup_flush - finish queue file */
221
cleanup_flush(CLEANUP_STATE * state)222 int cleanup_flush(CLEANUP_STATE *state)
223 {
224 int status;
225 char *junk;
226 VSTRING *trace_junk;
227
228 /*
229 * Raise these errors only if we examined all queue file records.
230 */
231 if (CLEANUP_OUT_OK(state)) {
232 if (state->recip == 0)
233 state->errs |= CLEANUP_STAT_RCPT;
234 if ((state->flags & CLEANUP_FLAG_END_SEEN) == 0)
235 state->errs |= CLEANUP_STAT_BAD;
236 }
237
238 /*
239 * Status sanitization. Always report success when the discard flag was
240 * raised by some user-specified access rule.
241 */
242 if (state->flags & CLEANUP_FLAG_DISCARD)
243 state->errs = 0;
244
245 /*
246 * Apply external mail filter.
247 *
248 * XXX Include test for a built-in action to tempfail this message.
249 */
250 if (CLEANUP_MILTER_OK(state)) {
251 if (state->milters)
252 cleanup_milter_inspect(state, state->milters);
253 else if (cleanup_milters) {
254 cleanup_milter_emul_data(state, cleanup_milters);
255 if (CLEANUP_MILTER_OK(state))
256 cleanup_milter_inspect(state, cleanup_milters);
257 }
258 }
259
260 /*
261 * Update the preliminary message size and count fields with the actual
262 * values.
263 */
264 if (CLEANUP_OUT_OK(state))
265 cleanup_final(state);
266
267 /*
268 * If there was an error that requires us to generate a bounce message
269 * (mail submitted with the Postfix sendmail command, mail forwarded by
270 * the local(8) delivery agent, or mail re-queued with "postsuper -r"),
271 * send a bounce notification, reset the error flags in case of success,
272 * and request deletion of the incoming queue file and of the optional
273 * DSN SUCCESS records from virtual alias expansion.
274 *
275 * XXX It would make no sense to knowingly report success after we already
276 * have bounced all recipients, especially because the information in the
277 * DSN SUCCESS notice is completely redundant compared to the information
278 * in the bounce notice (however, both may be incomplete when the queue
279 * file size would exceed the safety limit).
280 *
281 * An alternative is to keep the DSN SUCCESS records and to delegate bounce
282 * notification to the queue manager, just like we already delegate
283 * success notification. This requires that we leave the undeliverable
284 * message in the incoming queue; versions up to 20050726 did exactly
285 * that. Unfortunately, this broke with over-size queue files, because
286 * the queue manager cannot handle incomplete queue files (and it should
287 * not try to do so).
288 */
289 #define CAN_BOUNCE() \
290 ((state->errs & CLEANUP_STAT_MASK_CANT_BOUNCE) == 0 \
291 && state->sender != 0 \
292 && (state->flags & CLEANUP_FLAG_BOUNCE) != 0)
293
294 if (state->errs != 0 && CAN_BOUNCE())
295 cleanup_bounce(state);
296
297 /*
298 * Optionally, place the message on hold, but only if the message was
299 * received successfully and only if it's not being discarded for other
300 * reasons. This involves renaming the queue file before "finishing" it
301 * (or else the queue manager would grab it too early) and updating our
302 * own idea of the queue file name for error recovery and for error
303 * reporting purposes.
304 *
305 * XXX Include test for a built-in action to tempfail this message.
306 */
307 if (state->errs == 0 && (state->flags & CLEANUP_FLAG_DISCARD) == 0) {
308 if ((state->flags & CLEANUP_FLAG_HOLD) != 0
309 #ifdef DELAY_ACTION
310 || state->defer_delay > 0
311 #endif
312 ) {
313 myfree(state->queue_name);
314 #ifdef DELAY_ACTION
315 state->queue_name = mystrdup((state->flags & CLEANUP_FLAG_HOLD) ?
316 MAIL_QUEUE_HOLD : MAIL_QUEUE_DEFERRED);
317 #else
318 state->queue_name = mystrdup(MAIL_QUEUE_HOLD);
319 #endif
320 mail_stream_ctl(state->handle,
321 CA_MAIL_STREAM_CTL_QUEUE(state->queue_name),
322 CA_MAIL_STREAM_CTL_CLASS((char *) 0),
323 CA_MAIL_STREAM_CTL_SERVICE((char *) 0),
324 #ifdef DELAY_ACTION
325 CA_MAIL_STREAM_CTL_DELAY(state->defer_delay),
326 #endif
327 CA_MAIL_STREAM_CTL_END);
328 junk = cleanup_path;
329 cleanup_path = mystrdup(VSTREAM_PATH(state->handle->stream));
330 myfree(junk);
331
332 /*
333 * XXX: When delivering to a non-incoming queue, do not consume
334 * in_flow tokens. Unfortunately we can't move the code that
335 * consumes tokens until after the mail is received, because that
336 * would increase the risk of duplicate deliveries (RFC 1047).
337 */
338 (void) mail_flow_put(1);
339 }
340 state->errs = mail_stream_finish(state->handle, (VSTRING *) 0);
341 } else {
342
343 /*
344 * XXX: When discarding mail, should we consume in_flow tokens? See
345 * also the comments above for mail that is placed on hold.
346 */
347 #if 0
348 (void) mail_flow_put(1);
349 #endif
350 mail_stream_cleanup(state->handle);
351 }
352 state->handle = 0;
353 state->dst = 0;
354
355 /*
356 * If there was an error, or if the message must be discarded for other
357 * reasons, remove the queue file and the optional trace file with DSN
358 * SUCCESS records from virtual alias expansion.
359 */
360 if (state->errs != 0 || (state->flags & CLEANUP_FLAG_DISCARD) != 0) {
361 if (cleanup_trace_path)
362 (void) REMOVE(vstring_str(cleanup_trace_path));
363 if (REMOVE(cleanup_path))
364 msg_warn("remove %s: %m", cleanup_path);
365 msg_info("%s: removed (%s)", state->queue_id, state->errs ?
366 "canceled" : "discarded");
367 }
368
369 /*
370 * Make sure that our queue file will not be deleted by the error handler
371 * AFTER we have taken responsibility for delivery. Better to deliver
372 * twice than to lose mail.
373 */
374 trace_junk = cleanup_trace_path;
375 cleanup_trace_path = 0; /* don't delete upon error */
376 junk = cleanup_path;
377 cleanup_path = 0; /* don't delete upon error */
378
379 if (trace_junk)
380 vstring_free(trace_junk);
381 myfree(junk);
382
383 /*
384 * Cleanup internal state. This is simply complementary to the
385 * initializations at the beginning of cleanup_open().
386 */
387 if (msg_verbose)
388 msg_info("cleanup_flush: status %d", state->errs);
389 status = state->errs;
390 return (status);
391 }
392
393 /* cleanup_free - pay the last respects */
394
cleanup_free(CLEANUP_STATE * state)395 void cleanup_free(CLEANUP_STATE *state)
396 {
397
398 /*
399 * Emulate disconnect event. CLEANUP_FLAG_MILTER may be turned off after
400 * we have started.
401 */
402 if (cleanup_milters != 0 && state->milters == 0)
403 milter_disc_event(cleanup_milters);
404 cleanup_state_free(state);
405 }
406