1/* $NetBSD$ */ 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 55/* System library. */ 56 57#include <sys_defs.h> 58#include <sys/time.h> 59#include <unistd.h> 60 61/* Utility library. */ 62 63#include <msg.h> 64#include <mymalloc.h> 65#include <htable.h> 66#include <argv.h> 67#include <vstring.h> 68#include <vstream.h> 69#include <vstring_vstream.h> 70#include <iostuff.h> 71#include <stringops.h> 72 73/* Global library. */ 74 75#include <mail_proto.h> 76#include <cleanup_user.h> 77#include <sent.h> 78#include <record.h> 79#include <rec_type.h> 80#include <mark_corrupt.h> 81#include <mail_date.h> 82#include <mail_params.h> 83#include <dsn_mask.h> 84 85/* Application-specific. */ 86 87#include "local.h" 88 89 /* 90 * Use one cleanup service connection for each (delivered to, sender) pair. 91 */ 92static HTABLE *forward_dt; 93 94typedef struct FORWARD_INFO { 95 VSTREAM *cleanup; /* clean up service handle */ 96 char *queue_id; /* forwarded message queue id */ 97 struct timeval posting_time; /* posting time */ 98} FORWARD_INFO; 99 100/* forward_init - prepare for forwarding */ 101 102int forward_init(void) 103{ 104 105 /* 106 * Sanity checks. 107 */ 108 if (forward_dt != 0) 109 msg_panic("forward_init: missing forward_finish call"); 110 111 forward_dt = htable_create(0); 112 return (0); 113} 114 115/* forward_open - open connection to cleanup service */ 116 117static FORWARD_INFO *forward_open(DELIVER_REQUEST *request, const char *sender) 118{ 119 VSTRING *buffer = vstring_alloc(100); 120 FORWARD_INFO *info; 121 VSTREAM *cleanup; 122 123 /* 124 * Contact the cleanup service and save the new mail queue id. Request 125 * that the cleanup service bounces bad messages to the sender so that we 126 * can avoid the trouble of bounce management. 127 * 128 * In case you wonder what kind of bounces, examples are "too many hops", 129 * "message too large", perhaps some others. The reason not to bounce 130 * ourselves is that we don't really know who the recipients are. 131 */ 132 cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING); 133 if (cleanup == 0) 134 return (0); 135 close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC); 136 if (attr_scan(cleanup, ATTR_FLAG_STRICT, 137 ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, buffer, 138 ATTR_TYPE_END) != 1) { 139 vstream_fclose(cleanup); 140 return (0); 141 } 142 info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO)); 143 info->cleanup = cleanup; 144 info->queue_id = mystrdup(STR(buffer)); 145 GETTIMEOFDAY(&info->posting_time); 146 147#define FORWARD_CLEANUP_FLAGS (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_INTERNAL) 148 149 attr_print(cleanup, ATTR_FLAG_NONE, 150 ATTR_TYPE_INT, MAIL_ATTR_FLAGS, FORWARD_CLEANUP_FLAGS, 151 ATTR_TYPE_END); 152 153 /* 154 * Send initial message envelope information. For bounces, set the 155 * designated sender: mailing list owner, posting user, whatever. 156 */ 157 rec_fprintf(cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, 158 REC_TYPE_TIME_ARG(info->posting_time)); 159 rec_fputs(cleanup, REC_TYPE_FROM, sender); 160 161 /* 162 * Don't send the original envelope ID or full/headers return mask if it 163 * was reset due to mailing list expansion. 164 */ 165 if (request->dsn_ret) 166 rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%d", 167 MAIL_ATTR_DSN_RET, request->dsn_ret); 168 if (request->dsn_envid && *(request->dsn_envid)) 169 rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s", 170 MAIL_ATTR_DSN_ENVID, request->dsn_envid); 171 172 /* 173 * Zero-length attribute values are place holders for unavailable 174 * attribute values. See qmgr_message.c. They are not meant to be 175 * propagated to queue files. 176 */ 177#define PASS_ATTR(fp, name, value) do { \ 178 if ((value) && *(value)) \ 179 rec_fprintf((fp), REC_TYPE_ATTR, "%s=%s", (name), (value)); \ 180 } while (0) 181 182 /* 183 * XXX encapsulate these as one object. 184 */ 185 PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_NAME, request->client_name); 186 PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr); 187 PASS_ATTR(cleanup, MAIL_ATTR_LOG_PROTO_NAME, request->client_proto); 188 PASS_ATTR(cleanup, MAIL_ATTR_LOG_HELO_NAME, request->client_helo); 189 PASS_ATTR(cleanup, MAIL_ATTR_SASL_METHOD, request->sasl_method); 190 PASS_ATTR(cleanup, MAIL_ATTR_SASL_USERNAME, request->sasl_username); 191 PASS_ATTR(cleanup, MAIL_ATTR_SASL_SENDER, request->sasl_sender); 192 PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident); 193 PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context); 194 195 vstring_free(buffer); 196 return (info); 197} 198 199/* forward_append - append recipient to message envelope */ 200 201int forward_append(DELIVER_ATTR attr) 202{ 203 FORWARD_INFO *info; 204 HTABLE *table_snd; 205 206 /* 207 * Sanity checks. 208 */ 209 if (msg_verbose) 210 msg_info("forward delivered=%s sender=%s recip=%s", 211 attr.delivered, attr.sender, attr.rcpt.address); 212 if (forward_dt == 0) 213 msg_panic("forward_append: missing forward_init call"); 214 215 /* 216 * In order to find the recipient list, first index a table by 217 * delivered-to header address, then by envelope sender address. 218 */ 219 if ((table_snd = (HTABLE *) htable_find(forward_dt, attr.delivered)) == 0) { 220 table_snd = htable_create(0); 221 htable_enter(forward_dt, attr.delivered, (char *) table_snd); 222 } 223 if ((info = (FORWARD_INFO *) htable_find(table_snd, attr.sender)) == 0) { 224 if ((info = forward_open(attr.request, attr.sender)) == 0) 225 return (-1); 226 htable_enter(table_snd, attr.sender, (char *) info); 227 } 228 229 /* 230 * Append the recipient to the message envelope. Don't send the original 231 * recipient or notification mask if it was reset due to mailing list 232 * expansion. 233 */ 234 if (*attr.rcpt.dsn_orcpt) 235 rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%s", 236 MAIL_ATTR_DSN_ORCPT, attr.rcpt.dsn_orcpt); 237 if (attr.rcpt.dsn_notify) 238 rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%d", 239 MAIL_ATTR_DSN_NOTIFY, attr.rcpt.dsn_notify); 240 if (*attr.rcpt.orig_addr) 241 rec_fputs(info->cleanup, REC_TYPE_ORCP, attr.rcpt.orig_addr); 242 rec_fputs(info->cleanup, REC_TYPE_RCPT, attr.rcpt.address); 243 244 return (vstream_ferror(info->cleanup)); 245} 246 247/* forward_send - send forwarded message */ 248 249static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request, 250 DELIVER_ATTR attr, char *delivered) 251{ 252 const char *myname = "forward_send"; 253 VSTRING *buffer = vstring_alloc(100); 254 int status; 255 int rec_type = 0; 256 257 /* 258 * Start the message content segment. Prepend our Delivered-To: header to 259 * the message data. Stop at the first error. XXX Rely on the front-end 260 * services to enforce record size limits. 261 */ 262 rec_fputs(info->cleanup, REC_TYPE_MESG, ""); 263 vstring_strcpy(buffer, delivered); 264 rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)", 265 var_myhostname, var_mail_name); 266 rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s", 267 info->queue_id, mail_date(info->posting_time.tv_sec)); 268 if (local_deliver_hdr_mask & DELIVER_HDR_FWD) 269 rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s", 270 lowercase(STR(buffer))); 271 if ((status = vstream_ferror(info->cleanup)) == 0) 272 if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0) 273 msg_fatal("%s: seek queue file %s: %m:", 274 myname, VSTREAM_PATH(attr.fp)); 275 while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) { 276 if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM) 277 break; 278 status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type); 279 } 280 if (status == 0 && rec_type != REC_TYPE_XTRA) { 281 msg_warn("%s: bad record type: %d in message content", 282 info->queue_id, rec_type); 283 status |= mark_corrupt(attr.fp); 284 } 285 286 /* 287 * Send the end-of-data marker only when there were no errors. 288 */ 289 if (status == 0) { 290 rec_fputs(info->cleanup, REC_TYPE_XTRA, ""); 291 rec_fputs(info->cleanup, REC_TYPE_END, ""); 292 } 293 294 /* 295 * Retrieve the cleanup service completion status only if there are no 296 * problems. 297 */ 298 if (status == 0) 299 if (vstream_fflush(info->cleanup) 300 || attr_scan(info->cleanup, ATTR_FLAG_MISSING, 301 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, 302 ATTR_TYPE_END) != 1) 303 status = 1; 304 305 /* 306 * Log successful forwarding. 307 * 308 * XXX DSN alias and .forward expansion already report SUCCESS, so don't do 309 * it again here. 310 */ 311 if (status == 0) { 312 attr.rcpt.dsn_notify = 313 (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ? 314 DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS); 315 dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, 316 "forwarded as %s", info->queue_id); 317 status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr)); 318 } 319 320 /* 321 * Cleanup. 322 */ 323 vstring_free(buffer); 324 return (status); 325} 326 327/* forward_finish - complete message forwarding requests and clean up */ 328 329int forward_finish(DELIVER_REQUEST *request, DELIVER_ATTR attr, int cancel) 330{ 331 HTABLE_INFO **dt_list; 332 HTABLE_INFO **dt; 333 HTABLE_INFO **sn_list; 334 HTABLE_INFO **sn; 335 HTABLE *table_snd; 336 char *delivered; 337 char *sender; 338 FORWARD_INFO *info; 339 int status = cancel; 340 341 /* 342 * Sanity checks. 343 */ 344 if (forward_dt == 0) 345 msg_panic("forward_finish: missing forward_init call"); 346 347 /* 348 * Walk over all delivered-to header addresses and over each envelope 349 * sender address. 350 */ 351 for (dt = dt_list = htable_list(forward_dt); *dt; dt++) { 352 delivered = dt[0]->key; 353 table_snd = (HTABLE *) dt[0]->value; 354 for (sn = sn_list = htable_list(table_snd); *sn; sn++) { 355 sender = sn[0]->key; 356 info = (FORWARD_INFO *) sn[0]->value; 357 if (status == 0) 358 status |= forward_send(info, request, attr, delivered); 359 if (msg_verbose) 360 msg_info("forward_finish: delivered %s sender %s status %d", 361 delivered, sender, status); 362 (void) vstream_fclose(info->cleanup); 363 myfree(info->queue_id); 364 myfree((char *) info); 365 } 366 myfree((char *) sn_list); 367 htable_free(table_snd, (void (*) (char *)) 0); 368 } 369 myfree((char *) dt_list); 370 htable_free(forward_dt, (void (*) (char *)) 0); 371 forward_dt = 0; 372 return (status); 373} 374