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