1 /* $NetBSD: bounce.c,v 1.5 2026/05/09 18:49:15 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* bounce 3 6 /* SUMMARY 7 /* bounce service client 8 /* SYNOPSIS 9 /* #include <bounce.h> 10 /* 11 /* int bounce_append(flags, id, stats, recipient, relay, tstats, dsn) 12 /* int flags; 13 /* const char *id; 14 /* MSG_STATS *stats; 15 /* RECIPIENT *rcpt; 16 /* const char *relay; 17 /* const POL_STATS *tstats; 18 /* DSN *dsn; 19 /* 20 /* int bounce_flush(flags, queue, id, encoding, sendopts, sender, 21 /* dsn_envid, dsn_ret) 22 /* int flags; 23 /* const char *queue; 24 /* const char *id; 25 /* const char *encoding; 26 /* int sendopts; 27 /* const char *sender; 28 /* const char *dsn_envid; 29 /* int dsn_ret; 30 /* 31 /* int bounce_flush_verp(flags, queue, id, encoding, sendopts, 32 /* sender, dsn_envid, dsn_ret, verp_delims) 33 /* int flags; 34 /* const char *queue; 35 /* const char *id; 36 /* const char *encoding; 37 /* int sendopts; 38 /* const char *sender; 39 /* const char *dsn_envid; 40 /* int dsn_ret; 41 /* const char *verp_delims; 42 /* 43 /* int bounce_one(flags, queue, id, encoding, sendopts, sender, 44 /* dsn_envid, ret, stats, recipient, relay, 45 /* tstats, dsn) 46 /* int flags; 47 /* const char *queue; 48 /* const char *id; 49 /* const char *encoding; 50 /* int sendopts; 51 /* const char *sender; 52 /* const char *dsn_envid; 53 /* int dsn_ret; 54 /* MSG_STATS *stats; 55 /* RECIPIENT *rcpt; 56 /* const char *relay; 57 /* const POL_STATS *tstats; 58 /* DSN *dsn; 59 /* 60 /* void bounce_client_init(title, maps) 61 /* const char *title; 62 /* const char *maps; 63 /* INTERNAL API 64 /* DSN_FILTER *delivery_status_filter; 65 /* 66 /* int bounce_append_intern(flags, id, stats, recipient, relay, tstats, dsn) 67 /* int flags; 68 /* const char *id; 69 /* MSG_STATS *stats; 70 /* RECIPIENT *rcpt; 71 /* const char *relay; 72 /* const POL_STATS *tstats; 73 /* DSN *dsn; 74 /* 75 /* int bounce_one_intern(flags, queue, id, encoding, sendopts, sender, 76 /* dsn_envid, ret, stats, recipient, relay, tstats, dsn) 77 /* int flags; 78 /* const char *queue; 79 /* const char *id; 80 /* const char *encoding; 81 /* int sendopts; 82 /* const char *sender; 83 /* const char *dsn_envid; 84 /* int dsn_ret; 85 /* MSG_STATS *stats; 86 /* RECIPIENT *rcpt; 87 /* const char *relay; 88 /* const POL_STATS *tstats; 89 /* DSN *dsn; 90 /* DESCRIPTION 91 /* This module implements the client interface to the message 92 /* bounce service, which maintains a per-message log of status 93 /* records with recipients that were bounced, and the dsn_text why. 94 /* 95 /* bounce_append() appends a dsn_text for non-delivery to the 96 /* bounce log for the named recipient, updates the address 97 /* verification service, or updates a message delivery record 98 /* on request by the sender. The flags argument determines 99 /* the action. 100 /* 101 /* bounce_flush() actually bounces the specified message to 102 /* the specified sender, including the bounce log that was 103 /* built with bounce_append(). The bounce logfile is removed 104 /* upon successful completion. 105 /* 106 /* bounce_flush_verp() is like bounce_flush(), but sends one 107 /* notification per recipient, with the failed recipient encoded 108 /* into the sender address. 109 /* 110 /* bounce_one() bounces one recipient and immediately sends a 111 /* notification to the sender. This procedure does not append 112 /* the recipient and dsn_text to the per-message bounce log, and 113 /* should be used when a delivery agent changes the error 114 /* return address in a manner that depends on the recipient 115 /* address. 116 /* 117 /* bounce_client_init() initializes an optional DSN filter. 118 /* 119 /* bounce_append_intern() and bounce_one_intern() are for use 120 /* after the DSN filter. 121 /* 122 /* Arguments: 123 /* .IP flags 124 /* The bitwise OR of zero or more of the following (specify 125 /* BOUNCE_FLAG_NONE to request no special processing): 126 /* .RS 127 /* .IP BOUNCE_FLAG_CLEAN 128 /* Delete the bounce log in case of an error (as in: pretend 129 /* that we never even tried to bounce this message). 130 /* .IP BOUNCE_FLAG_DELRCPT 131 /* When specified with a flush request, request that 132 /* recipients be deleted from the queue file. 133 /* 134 /* Note: the bounce daemon ignores this request when the 135 /* recipient queue file offset is <= 0. 136 /* .IP DEL_REQ_FLAG_MTA_VRFY 137 /* The message is an MTA-requested address verification probe. 138 /* Update the address verification database instead of bouncing 139 /* mail. 140 /* .IP DEL_REQ_FLAG_USR_VRFY 141 /* The message is a user-requested address expansion probe. 142 /* Update the message delivery record instead of bouncing mail. 143 /* .IP DEL_REQ_FLAG_RECORD 144 /* This is a normal message with logged delivery. Update the 145 /* message delivery record and bounce the mail. 146 /* .RE 147 /* .IP queue 148 /* The message queue name of the original message file. 149 /* .IP id 150 /* The message queue id if the original message file. The bounce log 151 /* file has the same name as the original message file. 152 /* .IP stats 153 /* Time stamps from different message delivery stages 154 /* and session reuse count. 155 /* .IP rcpt 156 /* Recipient information. See recipient_list(3). 157 /* .IP relay 158 /* Name of the host that the message could not be delivered to. 159 /* This information is used for syslogging only. 160 /* .IP encoding 161 /* The body content encoding: MAIL_ATTR_ENC_{7BIT,8BIT,NONE}. 162 /* .IP sendopts 163 /* Sender-requested SMTPUTF8 or RequireTLS support. 164 /* .IP sender 165 /* The sender envelope address. 166 /* .IP tstats 167 /* TLS per-feature status. 168 /* .IP dsn_envid 169 /* Optional DSN envelope ID. 170 /* .IP dsn_ret 171 /* Optional DSN return full/headers option. 172 /* .IP dsn 173 /* Delivery status. See dsn(3). The specified action is ignored. 174 /* .IP verp_delims 175 /* VERP delimiter characters, used when encoding the failed 176 /* sender into the envelope sender address. 177 /* DIAGNOSTICS 178 /* In case of success, these functions log the action, and return a 179 /* zero value. Otherwise, the functions return a non-zero result, 180 /* and when BOUNCE_FLAG_CLEAN is disabled, log that message 181 /* delivery is deferred. 182 /* .IP title 183 /* The origin of the optional DSN filter lookup table names. 184 /* .IP maps 185 /* The optional "type:table" DSN filter lookup table names, 186 /* separated by comma or whitespace. 187 /* BUGS 188 /* Should be replaced by routines with an attribute-value based 189 /* interface instead of an interface that uses a rigid argument list. 190 /* LICENSE 191 /* .ad 192 /* .fi 193 /* The Secure Mailer license must be distributed with this software. 194 /* AUTHOR(S) 195 /* Wietse Venema 196 /* IBM T.J. Watson Research 197 /* P.O. Box 704 198 /* Yorktown Heights, NY 10598, USA 199 /* 200 /* Wietse Venema 201 /* Google, Inc. 202 /* 111 8th Avenue 203 /* New York, NY 10011, USA 204 /* 205 /* Wietse Venema 206 /* porcupine.org 207 /*--*/ 208 209 /* System library. */ 210 211 #include <sys_defs.h> 212 #include <string.h> 213 214 /* Utility library. */ 215 216 #include <msg.h> 217 #include <vstring.h> 218 #include <mymalloc.h> 219 220 /* Global library. */ 221 222 #define DSN_INTERN 223 #include <mail_params.h> 224 #include <mail_proto.h> 225 #include <log_adhoc.h> 226 #include <dsn_util.h> 227 #include <rcpt_print.h> 228 #include <dsn_print.h> 229 #include <verify.h> 230 #include <defer.h> 231 #include <trace.h> 232 #include <bounce.h> 233 234 /* Shared internally, between bounce and defer clients. */ 235 236 DSN_FILTER *delivery_status_filter; 237 238 /* bounce_append - append delivery status to per-message bounce log */ 239 240 int bounce_append(int flags, const char *id, MSG_STATS *stats, 241 RECIPIENT *rcpt, const char *relay, 242 const POL_STATS *tstats, DSN *dsn) 243 { 244 DSN my_dsn = *dsn; 245 DSN *dsn_res; 246 247 /* 248 * Sanity check. If we're really confident, change this into msg_panic 249 * (remember, this information may be under control by a hostile server). 250 */ 251 if (my_dsn.status[0] != '5' || !dsn_valid(my_dsn.status)) { 252 msg_warn("bounce_append: ignoring dsn code \"%s\"", my_dsn.status); 253 my_dsn.status = "5.0.0"; 254 } 255 256 /* 257 * DSN filter (Postfix 3.0). 258 */ 259 if (delivery_status_filter != 0 260 && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0) { 261 if (dsn_res->status[0] == '4') 262 return (defer_append_intern(flags, id, stats, rcpt, relay, tstats, 263 dsn_res)); 264 my_dsn = *dsn_res; 265 } 266 return (bounce_append_intern(flags, id, stats, rcpt, relay, tstats, 267 &my_dsn)); 268 } 269 270 /* bounce_append_intern - append delivery status to per-message bounce log */ 271 272 int bounce_append_intern(int flags, const char *id, MSG_STATS *stats, 273 RECIPIENT *rcpt, const char *relay, 274 const POL_STATS *tstats, DSN *dsn) 275 { 276 DSN my_dsn = *dsn; 277 int status; 278 279 /* 280 * MTA-requested address verification information is stored in the verify 281 * service database. 282 */ 283 if (flags & DEL_REQ_FLAG_MTA_VRFY) { 284 my_dsn.action = "undeliverable"; 285 status = verify_append(id, stats, rcpt, relay, tstats, &my_dsn, 286 DEL_RCPT_STAT_BOUNCE); 287 return (status); 288 } 289 290 /* 291 * User-requested address verification information is logged and mailed 292 * to the requesting user. 293 */ 294 if (flags & DEL_REQ_FLAG_USR_VRFY) { 295 my_dsn.action = "undeliverable"; 296 status = trace_append(flags, id, stats, rcpt, relay, tstats, &my_dsn); 297 return (status); 298 } 299 300 /* 301 * Normal (well almost) delivery. When we're pretending that we can't 302 * bounce, don't create a defer log file when we wouldn't keep the bounce 303 * log file. That's a lot of negatives in one sentence. 304 */ 305 else if (var_soft_bounce && (flags & BOUNCE_FLAG_CLEAN)) { 306 return (-1); 307 } 308 309 /* 310 * Normal mail delivery. May also send a delivery record to the user. 311 * 312 * XXX DSN We write all recipients to the bounce logfile regardless of DSN 313 * NOTIFY options, because those options don't apply to postmaster 314 * notifications. 315 */ 316 else { 317 char *my_status = mystrdup(my_dsn.status); 318 const char *log_status = var_soft_bounce ? "SOFTBOUNCE" : "bounced"; 319 320 /* 321 * Supply default action. 322 */ 323 my_dsn.status = my_status; 324 if (var_soft_bounce) { 325 my_status[0] = '4'; 326 my_dsn.action = "delayed"; 327 } else { 328 my_dsn.action = "failed"; 329 } 330 331 if (mail_command_client(MAIL_CLASS_PRIVATE, var_soft_bounce ? 332 var_defer_service : var_bounce_service, 333 MAIL_ATTR_PROTO_BOUNCE, 334 SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND), 335 SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), 336 SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id), 337 SEND_ATTR_FUNC(rcpt_print, (const void *) rcpt), 338 SEND_ATTR_FUNC(dsn_print, (const void *) &my_dsn), 339 ATTR_TYPE_END) == 0 340 && ((flags & DEL_REQ_FLAG_RECORD) == 0 341 || trace_append(flags, id, stats, rcpt, relay, tstats, 342 &my_dsn) == 0)) { 343 log_adhoc(id, stats, rcpt, relay, tstats, &my_dsn, log_status); 344 status = (var_soft_bounce ? -1 : 0); 345 } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) { 346 VSTRING *junk = vstring_alloc(100); 347 348 my_dsn.status = "4.3.0"; 349 vstring_sprintf(junk, "%s or %s service failure", 350 var_bounce_service, var_trace_service); 351 my_dsn.reason = vstring_str(junk); 352 status = defer_append_intern(flags, id, stats, rcpt, relay, tstats, 353 &my_dsn); 354 vstring_free(junk); 355 } else { 356 status = -1; 357 } 358 myfree(my_status); 359 return (status); 360 } 361 } 362 363 /* bounce_flush - flush the bounce log and deliver to the sender */ 364 365 int bounce_flush(int flags, const char *queue, const char *id, 366 const char *encoding, int sendopts, 367 const char *sender, const char *dsn_envid, 368 int dsn_ret) 369 { 370 371 /* 372 * When we're pretending that we can't bounce, don't send a bounce 373 * message. 374 */ 375 if (var_soft_bounce) 376 return (-1); 377 if (mail_command_client(MAIL_CLASS_PRIVATE, var_bounce_service, 378 MAIL_ATTR_PROTO_BOUNCE, 379 SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_FLUSH), 380 SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), 381 SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue), 382 SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id), 383 SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding), 384 SEND_ATTR_INT(MAIL_ATTR_SENDOPTS, sendopts), 385 SEND_ATTR_STR(MAIL_ATTR_SENDER, sender), 386 SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid), 387 SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret), 388 ATTR_TYPE_END) == 0) { 389 return (0); 390 } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) { 391 msg_info("%s: status=deferred (bounce failed)", id); 392 return (-1); 393 } else { 394 return (-1); 395 } 396 } 397 398 /* bounce_flush_verp - verpified notification */ 399 400 int bounce_flush_verp(int flags, const char *queue, const char *id, 401 const char *encoding, int sendopts, 402 const char *sender, const char *dsn_envid, 403 int dsn_ret, const char *verp_delims) 404 { 405 406 /* 407 * When we're pretending that we can't bounce, don't send a bounce 408 * message. 409 */ 410 if (var_soft_bounce) 411 return (-1); 412 if (mail_command_client(MAIL_CLASS_PRIVATE, var_bounce_service, 413 MAIL_ATTR_PROTO_BOUNCE, 414 SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_VERP), 415 SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), 416 SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue), 417 SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id), 418 SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding), 419 SEND_ATTR_INT(MAIL_ATTR_SENDOPTS, sendopts), 420 SEND_ATTR_STR(MAIL_ATTR_SENDER, sender), 421 SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid), 422 SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret), 423 SEND_ATTR_STR(MAIL_ATTR_VERPDL, verp_delims), 424 ATTR_TYPE_END) == 0) { 425 return (0); 426 } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) { 427 msg_info("%s: status=deferred (bounce failed)", id); 428 return (-1); 429 } else { 430 return (-1); 431 } 432 } 433 434 /* bounce_one - send notice for one recipient */ 435 436 int bounce_one(int flags, const char *queue, const char *id, 437 const char *encoding, int sendopts, 438 const char *sender, const char *dsn_envid, 439 int dsn_ret, MSG_STATS *stats, RECIPIENT *rcpt, 440 const char *relay, const POL_STATS *tstats, DSN *dsn) 441 { 442 DSN my_dsn = *dsn; 443 DSN *dsn_res; 444 445 /* 446 * Sanity check. 447 */ 448 if (my_dsn.status[0] != '5' || !dsn_valid(my_dsn.status)) { 449 msg_warn("bounce_one: ignoring dsn code \"%s\"", my_dsn.status); 450 my_dsn.status = "5.0.0"; 451 } 452 453 /* 454 * DSN filter (Postfix 3.0). 455 */ 456 if (delivery_status_filter != 0 457 && (dsn_res = dsn_filter_lookup(delivery_status_filter, &my_dsn)) != 0) { 458 if (dsn_res->status[0] == '4') 459 return (defer_append_intern(flags, id, stats, rcpt, relay, tstats, 460 dsn_res)); 461 my_dsn = *dsn_res; 462 } 463 return (bounce_one_intern(flags, queue, id, encoding, sendopts, sender, 464 dsn_envid, dsn_ret, stats, rcpt, relay, tstats, 465 &my_dsn)); 466 } 467 468 /* bounce_one_intern - send notice for one recipient */ 469 470 int bounce_one_intern(int flags, const char *queue, const char *id, 471 const char *encoding, int sendopts, 472 const char *sender, const char *dsn_envid, 473 int dsn_ret, MSG_STATS *stats, 474 RECIPIENT *rcpt, const char *relay, 475 const POL_STATS *tstats, DSN *dsn) 476 { 477 DSN my_dsn = *dsn; 478 int status; 479 480 /* 481 * MTA-requested address verification information is stored in the verify 482 * service database. 483 */ 484 if (flags & DEL_REQ_FLAG_MTA_VRFY) { 485 my_dsn.action = "undeliverable"; 486 status = verify_append(id, stats, rcpt, relay, tstats, &my_dsn, 487 DEL_RCPT_STAT_BOUNCE); 488 return (status); 489 } 490 491 /* 492 * User-requested address verification information is logged and mailed 493 * to the requesting user. 494 */ 495 if (flags & DEL_REQ_FLAG_USR_VRFY) { 496 my_dsn.action = "undeliverable"; 497 status = trace_append(flags, id, stats, rcpt, relay, tstats, &my_dsn); 498 return (status); 499 } 500 501 /* 502 * When we're not bouncing, then use the standard multi-recipient logfile 503 * based procedure. 504 */ 505 else if (var_soft_bounce) { 506 return (bounce_append_intern(flags, id, stats, rcpt, relay, tstats, 507 &my_dsn)); 508 } 509 510 /* 511 * Normal mail delivery. May also send a delivery record to the user. 512 * 513 * XXX DSN We send all recipients regardless of DSN NOTIFY options, because 514 * those options don't apply to postmaster notifications. 515 */ 516 else { 517 518 /* 519 * Supply default action. 520 */ 521 my_dsn.action = "failed"; 522 523 if (mail_command_client(MAIL_CLASS_PRIVATE, var_bounce_service, 524 MAIL_ATTR_PROTO_BOUNCE, 525 SEND_ATTR_INT(MAIL_ATTR_NREQ, BOUNCE_CMD_ONE), 526 SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), 527 SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue), 528 SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id), 529 SEND_ATTR_STR(MAIL_ATTR_ENCODING, encoding), 530 SEND_ATTR_INT(MAIL_ATTR_SENDOPTS, sendopts), 531 SEND_ATTR_STR(MAIL_ATTR_SENDER, sender), 532 SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid), 533 SEND_ATTR_INT(MAIL_ATTR_DSN_RET, dsn_ret), 534 SEND_ATTR_FUNC(rcpt_print, (const void *) rcpt), 535 SEND_ATTR_FUNC(dsn_print, (const void *) &my_dsn), 536 ATTR_TYPE_END) == 0 537 && ((flags & DEL_REQ_FLAG_RECORD) == 0 538 || trace_append(flags, id, stats, rcpt, relay, tstats, 539 &my_dsn) == 0)) { 540 log_adhoc(id, stats, rcpt, relay, tstats, &my_dsn, "bounced"); 541 status = 0; 542 } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) { 543 VSTRING *junk = vstring_alloc(100); 544 545 my_dsn.status = "4.3.0"; 546 vstring_sprintf(junk, "%s or %s service failure", 547 var_bounce_service, var_trace_service); 548 my_dsn.reason = vstring_str(junk); 549 status = defer_append_intern(flags, id, stats, rcpt, relay, tstats, 550 &my_dsn); 551 vstring_free(junk); 552 } else { 553 status = -1; 554 } 555 return (status); 556 } 557 } 558 559 /* bounce_client_init - initialize bounce/defer DSN filter */ 560 561 void bounce_client_init(const char *title, const char *maps) 562 { 563 static const char myname[] = "bounce_client_init"; 564 565 if (delivery_status_filter != 0) 566 msg_panic("%s: duplicate initialization", myname); 567 if (*maps) 568 delivery_status_filter = dsn_filter_create(title, maps); 569 } 570