Home | History | Annotate | Line # | Download | only in local
      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