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