1 /* $NetBSD: forward.c,v 1.6 2026/05/09 18:49:17 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* forward 3 6 /* SUMMARY 7 /* message forwarding 8 /* SYNOPSIS 9 /* #include "local.h" 10 /* 11 /* int forward_init() 12 /* 13 /* int forward_append(attr) 14 /* DELIVER_ATTR attr; 15 /* 16 /* int forward_finish(request, attr, cancel) 17 /* DELIVER_REQUEST *request; 18 /* DELIVER_ATTR attr; 19 /* int cancel; 20 /* DESCRIPTION 21 /* This module implements the client interface for message 22 /* forwarding. 23 /* 24 /* forward_init() initializes internal data structures. 25 /* 26 /* forward_append() appends a recipient to the list of recipients 27 /* that will receive a message with the specified message sender 28 /* and delivered-to addresses. 29 /* 30 /* forward_finish() forwards the actual message contents and 31 /* releases the memory allocated by forward_init() and by 32 /* forward_append(). When the \fIcancel\fR argument is true, no 33 /* messages will be forwarded. The \fIattr\fR argument specifies 34 /* the original message delivery attributes as they were before 35 /* alias or forward expansions. 36 /* DIAGNOSTICS 37 /* A non-zero result means that the requested operation should 38 /* be tried again. 39 /* Warnings: problems connecting to the forwarding service, 40 /* corrupt message file. A corrupt message is saved to the 41 /* "corrupt" queue for further inspection. 42 /* Fatal: out of memory. 43 /* Panic: missing forward_init() or forward_finish() call. 44 /* LICENSE 45 /* .ad 46 /* .fi 47 /* The Secure Mailer license must be distributed with this software. 48 /* AUTHOR(S) 49 /* Wietse Venema 50 /* IBM T.J. Watson Research 51 /* P.O. Box 704 52 /* Yorktown Heights, NY 10598, USA 53 /* 54 /* Wietse Venema 55 /* Google, Inc. 56 /* 111 8th Avenue 57 /* New York, NY 10011, USA 58 /* 59 /* Wietse Venema 60 /* porcupine.org 61 /*--*/ 62 63 /* System library. */ 64 65 #include <sys_defs.h> 66 #include <sys/time.h> 67 #include <unistd.h> 68 69 /* Utility library. */ 70 71 #include <msg.h> 72 #include <mymalloc.h> 73 #include <htable.h> 74 #include <argv.h> 75 #include <vstring.h> 76 #include <vstream.h> 77 #include <vstring_vstream.h> 78 #include <iostuff.h> 79 #include <stringops.h> 80 81 /* Global library. */ 82 83 #include <mail_proto.h> 84 #include <cleanup_user.h> 85 #include <sent.h> 86 #include <record.h> 87 #include <rec_type.h> 88 #include <mark_corrupt.h> 89 #include <mail_date.h> 90 #include <mail_params.h> 91 #include <dsn_mask.h> 92 #include <smtputf8.h> 93 94 /* Application-specific. */ 95 96 #include "local.h" 97 98 /* 99 * Use one cleanup service connection for each (delivered to, sender) pair. 100 */ 101 static HTABLE *forward_dt; 102 103 typedef struct FORWARD_INFO { 104 VSTREAM *cleanup; /* clean up service handle */ 105 char *queue_id; /* forwarded message queue id */ 106 struct timeval posting_time; /* posting time */ 107 } FORWARD_INFO; 108 109 /* forward_init - prepare for forwarding */ 110 111 int forward_init(void) 112 { 113 114 /* 115 * Sanity checks. 116 */ 117 if (forward_dt != 0) 118 msg_panic("forward_init: missing forward_finish call"); 119 120 forward_dt = htable_create(0); 121 return (0); 122 } 123 124 /* forward_open - open connection to cleanup service */ 125 126 static FORWARD_INFO *forward_open(DELIVER_REQUEST *request, const char *sender) 127 { 128 VSTRING *buffer = vstring_alloc(100); 129 FORWARD_INFO *info; 130 VSTREAM *cleanup; 131 132 #define FORWARD_OPEN_RETURN(res) do { \ 133 vstring_free(buffer); \ 134 return (res); \ 135 } while (0) 136 137 /* 138 * Contact the cleanup service and save the new mail queue id. Request 139 * that the cleanup service bounces bad messages to the sender so that we 140 * can avoid the trouble of bounce management. 141 * 142 * In case you wonder what kind of bounces, examples are "too many hops", 143 * "message too large", perhaps some others. The reason not to bounce 144 * ourselves is that we don't really know who the recipients are. 145 */ 146 cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING); 147 if (cleanup == 0) { 148 msg_warn("connect to %s/%s: %m", 149 MAIL_CLASS_PUBLIC, var_cleanup_service); 150 FORWARD_OPEN_RETURN(0); 151 } 152 close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC); 153 if (attr_scan(cleanup, ATTR_FLAG_STRICT, 154 RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP), 155 RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buffer), 156 ATTR_TYPE_END) != 1) { 157 vstream_fclose(cleanup); 158 FORWARD_OPEN_RETURN(0); 159 } 160 info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO)); 161 info->cleanup = cleanup; 162 info->queue_id = mystrdup(STR(buffer)); 163 GETTIMEOFDAY(&info->posting_time); 164 165 #define FORWARD_CLEANUP_FLAGS \ 166 (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_INTERNAL \ 167 | smtputf8_autodetect(MAIL_SRC_MASK_FORWARD) \ 168 | ((request->sendopts & SOPT_REQUIRETLS_ESMTP) ? \ 169 CLEANUP_FLAG_REQTLS : 0) \ 170 | ((request->sendopts & SMTPUTF8_FLAG_REQUESTED) ? \ 171 CLEANUP_FLAG_SMTPUTF8 : 0)) 172 173 attr_print(cleanup, ATTR_FLAG_NONE, 174 SEND_ATTR_INT(MAIL_ATTR_FLAGS, FORWARD_CLEANUP_FLAGS), 175 ATTR_TYPE_END); 176 177 /* 178 * Send initial message envelope information. For bounces, set the 179 * designated sender: mailing list owner, posting user, whatever. 180 */ 181 rec_fprintf(cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, 182 REC_TYPE_TIME_ARG(info->posting_time)); 183 rec_fputs(cleanup, REC_TYPE_FROM, sender); 184 185 /* 186 * Don't send the original envelope ID or full/headers return mask if it 187 * was reset due to mailing list expansion. 188 */ 189 if (request->dsn_ret) 190 rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%d", 191 MAIL_ATTR_DSN_RET, request->dsn_ret); 192 if (request->dsn_envid && *(request->dsn_envid)) 193 rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s", 194 MAIL_ATTR_DSN_ENVID, request->dsn_envid); 195 196 /* 197 * Zero-length attribute values are place holders for unavailable 198 * attribute values. See qmgr_message.c. They are not meant to be 199 * propagated to queue files. 200 */ 201 #define PASS_ATTR(fp, name, value) do { \ 202 if ((value) && *(value)) \ 203 rec_fprintf((fp), REC_TYPE_ATTR, "%s=%s", (name), (value)); \ 204 } while (0) 205 206 /* 207 * XXX encapsulate these as one object. 208 */ 209 PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_NAME, request->client_name); 210 PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr); 211 PASS_ATTR(cleanup, MAIL_ATTR_LOG_PROTO_NAME, request->client_proto); 212 PASS_ATTR(cleanup, MAIL_ATTR_LOG_HELO_NAME, request->client_helo); 213 PASS_ATTR(cleanup, MAIL_ATTR_SASL_METHOD, request->sasl_method); 214 PASS_ATTR(cleanup, MAIL_ATTR_SASL_USERNAME, request->sasl_username); 215 PASS_ATTR(cleanup, MAIL_ATTR_SASL_SENDER, request->sasl_sender); 216 PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident); 217 PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context); 218 219 FORWARD_OPEN_RETURN(info); 220 } 221 222 /* forward_append - append recipient to message envelope */ 223 224 int forward_append(DELIVER_ATTR attr) 225 { 226 FORWARD_INFO *info; 227 HTABLE *table_snd; 228 229 /* 230 * Sanity checks. 231 */ 232 if (msg_verbose) 233 msg_info("forward delivered=%s sender=%s recip=%s", 234 attr.delivered, attr.sender, attr.rcpt.address); 235 if (forward_dt == 0) 236 msg_panic("forward_append: missing forward_init call"); 237 238 /* 239 * In order to find the recipient list, first index a table by 240 * delivered-to header address, then by envelope sender address. 241 */ 242 if ((table_snd = (HTABLE *) htable_find(forward_dt, attr.delivered)) == 0) { 243 table_snd = htable_create(0); 244 htable_enter(forward_dt, attr.delivered, (void *) table_snd); 245 } 246 if ((info = (FORWARD_INFO *) htable_find(table_snd, attr.sender)) == 0) { 247 if ((info = forward_open(attr.request, attr.sender)) == 0) 248 return (-1); 249 htable_enter(table_snd, attr.sender, (void *) info); 250 } 251 252 /* 253 * Append the recipient to the message envelope. Don't send the original 254 * recipient or notification mask if it was reset due to mailing list 255 * expansion. 256 */ 257 if (*attr.rcpt.dsn_orcpt) 258 rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%s", 259 MAIL_ATTR_DSN_ORCPT, attr.rcpt.dsn_orcpt); 260 if (attr.rcpt.dsn_notify) 261 rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%d", 262 MAIL_ATTR_DSN_NOTIFY, attr.rcpt.dsn_notify); 263 if (*attr.rcpt.orig_addr) 264 rec_fputs(info->cleanup, REC_TYPE_ORCP, attr.rcpt.orig_addr); 265 rec_fputs(info->cleanup, REC_TYPE_RCPT, attr.rcpt.address); 266 267 return (vstream_ferror(info->cleanup)); 268 } 269 270 /* forward_send - send forwarded message */ 271 272 static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request, 273 DELIVER_ATTR attr, char *delivered) 274 { 275 const char *myname = "forward_send"; 276 VSTRING *buffer = vstring_alloc(100); 277 VSTRING *folded; 278 int status; 279 int rec_type = 0; 280 281 /* 282 * Start the message content segment. Prepend our Delivered-To: header to 283 * the message data. Stop at the first error. XXX Rely on the front-end 284 * services to enforce record size limits. 285 */ 286 rec_fputs(info->cleanup, REC_TYPE_MESG, ""); 287 vstring_strcpy(buffer, delivered); 288 rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)", 289 var_myhostname, var_mail_name); 290 rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s", 291 info->queue_id, mail_date(info->posting_time.tv_sec)); 292 if (local_deliver_hdr_mask & DELIVER_HDR_FWD) { 293 folded = vstring_alloc(100); 294 rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s", 295 casefold(folded, (STR(buffer)))); 296 vstring_free(folded); 297 } 298 if ((status = vstream_ferror(info->cleanup)) == 0) 299 if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0) 300 msg_fatal("%s: seek queue file %s: %m:", 301 myname, VSTREAM_PATH(attr.fp)); 302 while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) { 303 if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM) 304 break; 305 status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type); 306 } 307 if (status == 0 && rec_type != REC_TYPE_XTRA) { 308 msg_warn("%s: bad record type: %d in message content", 309 info->queue_id, rec_type); 310 status |= mark_corrupt(attr.fp); 311 } 312 313 /* 314 * Send the end-of-data marker only when there were no errors. 315 */ 316 if (status == 0) { 317 rec_fputs(info->cleanup, REC_TYPE_XTRA, ""); 318 rec_fputs(info->cleanup, REC_TYPE_END, ""); 319 } 320 321 /* 322 * Retrieve the cleanup service completion status only if there are no 323 * problems. 324 */ 325 if (status == 0) 326 if (vstream_fflush(info->cleanup) 327 || attr_scan(info->cleanup, ATTR_FLAG_MISSING, 328 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 329 ATTR_TYPE_END) != 1) 330 status = 1; 331 332 /* 333 * Log successful forwarding. 334 * 335 * XXX DSN alias and .forward expansion already report SUCCESS, so don't do 336 * it again here. 337 */ 338 if (status == 0) { 339 attr.rcpt.dsn_notify = 340 (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ? 341 DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS); 342 dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, 343 "forwarded as %s", info->queue_id); 344 status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr)); 345 } 346 347 /* 348 * Cleanup. 349 */ 350 vstring_free(buffer); 351 return (status); 352 } 353 354 /* forward_finish - complete message forwarding requests and clean up */ 355 356 int forward_finish(DELIVER_REQUEST *request, DELIVER_ATTR attr, int cancel) 357 { 358 HTABLE_INFO **dt_list; 359 HTABLE_INFO **dt; 360 HTABLE_INFO **sn_list; 361 HTABLE_INFO **sn; 362 HTABLE *table_snd; 363 char *delivered; 364 char *sender; 365 FORWARD_INFO *info; 366 int status = cancel; 367 368 /* 369 * Sanity checks. 370 */ 371 if (forward_dt == 0) 372 msg_panic("forward_finish: missing forward_init call"); 373 374 /* 375 * Walk over all delivered-to header addresses and over each envelope 376 * sender address. 377 */ 378 for (dt = dt_list = htable_list(forward_dt); *dt; dt++) { 379 delivered = dt[0]->key; 380 table_snd = (HTABLE *) dt[0]->value; 381 for (sn = sn_list = htable_list(table_snd); *sn; sn++) { 382 sender = sn[0]->key; 383 info = (FORWARD_INFO *) sn[0]->value; 384 if (status == 0) 385 status |= forward_send(info, request, attr, delivered); 386 if (msg_verbose) 387 msg_info("forward_finish: delivered %s sender %s status %d", 388 delivered, sender, status); 389 (void) vstream_fclose(info->cleanup); 390 myfree(info->queue_id); 391 myfree((void *) info); 392 } 393 myfree((void *) sn_list); 394 htable_free(table_snd, (void (*) (void *)) 0); 395 } 396 myfree((void *) dt_list); 397 htable_free(forward_dt, (void (*) (void *)) 0); 398 forward_dt = 0; 399 return (status); 400 } 401