Home | History | Annotate | Line # | Download | only in trivial-rewrite
      1 /*	$NetBSD: transport.c,v 1.4 2022/10/08 16:12:50 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	transport 3
      6 /* SUMMARY
      7 /*	transport mapping
      8 /* SYNOPSIS
      9 /*	#include "transport.h"
     10 /*
     11 /*	TRANSPORT_INFO *transport_pre_init(maps_name, maps)
     12 /*	const char *maps_name;
     13 /*	const char *maps;
     14 /*
     15 /*	void	transport_post_init(info)
     16 /*	TRANSPORT_INFO *info;
     17 /*
     18 /*	int	transport_lookup(info, address, rcpt_domain, channel, nexthop)
     19 /*	TRANSPORT_INFO *info;
     20 /*	const char *address;
     21 /*	const char *rcpt_domain;
     22 /*	VSTRING *channel;
     23 /*	VSTRING *nexthop;
     24 /*
     25 /*	void	transport_free(info);
     26 /*	TRANSPORT_INFO * info;
     27 /* DESCRIPTION
     28 /*	This module implements access to the table that maps transport
     29 /*	user@domain addresses to (channel, nexthop) tuples.
     30 /*
     31 /*	transport_pre_init() performs initializations that should be
     32 /*	done before the process enters the chroot jail, and
     33 /*	before calling transport_lookup().
     34 /*
     35 /*	transport_post_init() can be invoked after entering the chroot
     36 /*	jail, and must be called before calling transport_lookup().
     37 /*
     38 /*	transport_lookup() finds the channel and nexthop for the given
     39 /*	domain, and returns 1 if something was found.	Otherwise, 0
     40 /*	is returned.
     41 /* DIAGNOSTICS
     42 /*	info->transport_path->error is non-zero when the lookup
     43 /*	should be tried again.
     44 /* SEE ALSO
     45 /*	maps(3), multi-dictionary search
     46 /*	strip_addr(3), strip extension from address
     47 /*	transport(5), format of transport map
     48 /* CONFIGURATION PARAMETERS
     49 /*	transport_maps, names of maps to be searched.
     50 /* LICENSE
     51 /* .ad
     52 /* .fi
     53 /*	The Secure Mailer license must be distributed with this software.
     54 /* AUTHOR(S)
     55 /*	Wietse Venema
     56 /*	IBM T.J. Watson Research
     57 /*	P.O. Box 704
     58 /*	Yorktown Heights, NY 10598, USA
     59 /*
     60 /*	Wietse Venema
     61 /*	Google, Inc.
     62 /*	111 8th Avenue
     63 /*	New York, NY 10011, USA
     64 /*--*/
     65 
     66 /* System library. */
     67 
     68 #include <sys_defs.h>
     69 #include <string.h>
     70 
     71 /* Utility library. */
     72 
     73 #include <msg.h>
     74 #include <stringops.h>
     75 #include <mymalloc.h>
     76 #include <vstring.h>
     77 #include <split_at.h>
     78 #include <dict.h>
     79 #include <events.h>
     80 
     81 /* Global library. */
     82 
     83 #include <strip_addr.h>
     84 #include <mail_params.h>
     85 #include <mail_addr_find.h>
     86 #include <match_parent_style.h>
     87 #include <mail_proto.h>
     88 
     89 /* Application-specific. */
     90 
     91 #include "transport.h"
     92 
     93 static int transport_match_parent_style;
     94 
     95 #define STR(x)	vstring_str(x)
     96 
     97 static void transport_wildcard_init(TRANSPORT_INFO *);
     98 
     99 /* transport_pre_init - pre-jail initialization */
    100 
    101 TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name,
    102 				           const char *transport_maps)
    103 {
    104     TRANSPORT_INFO *tp;
    105 
    106     tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp));
    107     tp->transport_path = maps_create(transport_maps_name, transport_maps,
    108 				     DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
    109 				     | DICT_FLAG_NO_REGSUB
    110 				     | DICT_FLAG_UTF8_REQUEST);
    111     tp->wildcard_channel = tp->wildcard_nexthop = 0;
    112     tp->wildcard_errno = 0;
    113     tp->expire = 0;
    114     return (tp);
    115 }
    116 
    117 /* transport_post_init - post-jail initialization */
    118 
    119 void    transport_post_init(TRANSPORT_INFO *tp)
    120 {
    121     transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS);
    122     transport_wildcard_init(tp);
    123 }
    124 
    125 /* transport_free - destroy transport info */
    126 
    127 void    transport_free(TRANSPORT_INFO *tp)
    128 {
    129     if (tp->transport_path)
    130 	maps_free(tp->transport_path);
    131     if (tp->wildcard_channel)
    132 	vstring_free(tp->wildcard_channel);
    133     if (tp->wildcard_nexthop)
    134 	vstring_free(tp->wildcard_nexthop);
    135     myfree((void *) tp);
    136 }
    137 
    138 /* update_entry - update from transport table entry */
    139 
    140 static void update_entry(const char *new_channel, const char *new_nexthop,
    141 			         const char *rcpt_domain, VSTRING *channel,
    142 			         VSTRING *nexthop)
    143 {
    144 
    145     /*
    146      * :[nexthop] means don't change the channel, and don't change the
    147      * nexthop unless a non-default nexthop is specified. Thus, a right-hand
    148      * side of ":" is the transport table equivalent of a NOOP.
    149      */
    150     if (*new_channel == 0) {			/* :[nexthop] */
    151 	if (*new_nexthop != 0)
    152 	    vstring_strcpy(nexthop, new_nexthop);
    153     }
    154 
    155     /*
    156      * transport[:[nexthop]] means change the channel, and reset the nexthop
    157      * to the default unless a non-default nexthop is specified.
    158      */
    159     else {
    160 	vstring_strcpy(channel, new_channel);
    161 	if (*new_nexthop != 0)
    162 	    vstring_strcpy(nexthop, new_nexthop);
    163 	else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0
    164 		 && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0)
    165 	    vstring_strcpy(nexthop, rcpt_domain);
    166 	else
    167 	    vstring_strcpy(nexthop, "Address is undeliverable");
    168     }
    169 }
    170 
    171 /* parse_transport_entry - parse transport table entry */
    172 
    173 static void parse_transport_entry(const char *value, const char *rcpt_domain,
    174 				          VSTRING *channel, VSTRING *nexthop)
    175 {
    176     char   *saved_value;
    177     const char *host;
    178 
    179 #define FOUND		1
    180 #define NOTFOUND	0
    181 
    182     /*
    183      * It would be great if we could specify a recipient address in the
    184      * lookup result. Unfortunately, we cannot simply run the result through
    185      * a parser that recognizes "transport:user@domain" because the lookup
    186      * result can have arbitrary content (especially in the case of the error
    187      * mailer).
    188      */
    189     saved_value = mystrdup(value);
    190     host = split_at(saved_value, ':');
    191     update_entry(saved_value, host ? host : "", rcpt_domain, channel, nexthop);
    192     myfree(saved_value);
    193 }
    194 
    195 /* transport_wildcard_init - (re) initialize wild-card lookup result */
    196 
    197 static void transport_wildcard_init(TRANSPORT_INFO *tp)
    198 {
    199     VSTRING *channel = vstring_alloc(10);
    200     VSTRING *nexthop = vstring_alloc(10);
    201     const char *value;
    202 
    203     /*
    204      * Both channel and nexthop may be zero-length strings. Therefore we must
    205      * use something else to represent "wild-card does not exist". We use
    206      * null VSTRING pointers, for historical reasons.
    207      */
    208     if (tp->wildcard_channel)
    209 	vstring_free(tp->wildcard_channel);
    210     if (tp->wildcard_nexthop)
    211 	vstring_free(tp->wildcard_nexthop);
    212 
    213     /*
    214      * Technically, the wildcard lookup pattern is redundant. A static map
    215      * (keys always match, result is fixed string) could achieve the same:
    216      *
    217      * transport_maps = hash:/etc/postfix/transport static:xxx:yyy
    218      *
    219      * But the user interface of such an approach would be less intuitive. We
    220      * tolerate the continued existence of wildcard lookup patterns because
    221      * of human interface considerations.
    222      */
    223 #define WILDCARD	"*"
    224 #define FULL		0
    225 #define PARTIAL		DICT_FLAG_FIXED
    226 
    227     if ((value = maps_find(tp->transport_path, WILDCARD, FULL)) != 0) {
    228 	parse_transport_entry(value, "", channel, nexthop);
    229 	tp->wildcard_errno = 0;
    230 	tp->wildcard_channel = channel;
    231 	tp->wildcard_nexthop = nexthop;
    232 	if (msg_verbose)
    233 	    msg_info("wildcard_{chan:hop}={%s:%s}",
    234 		     vstring_str(channel), vstring_str(nexthop));
    235     } else {
    236 	tp->wildcard_errno = tp->transport_path->error;
    237 	vstring_free(channel);
    238 	vstring_free(nexthop);
    239 	tp->wildcard_channel = 0;
    240 	tp->wildcard_nexthop = 0;
    241     }
    242     tp->expire = event_time() + 30;		/* XXX make configurable */
    243 }
    244 
    245 /* transport_lookup - map a transport domain */
    246 
    247 int     transport_lookup(TRANSPORT_INFO *tp, const char *addr,
    248 			         const char *rcpt_domain,
    249 			         VSTRING *channel, VSTRING *nexthop)
    250 {
    251     char   *ratsign = 0;
    252     const char *value;
    253 
    254 #define STREQ(x,y)	(strcmp((x), (y)) == 0)
    255 #define DISCARD_EXTENSION ((char **) 0)
    256 
    257     /*
    258      * The null recipient is rewritten to the local mailer daemon address.
    259      */
    260     if (*addr == 0) {
    261 	msg_warn("transport_lookup: null address - skipping table lookup");
    262 	return (NOTFOUND);
    263     }
    264 
    265     /*
    266      * Look up the full and extension-stripped address, then match the domain
    267      * and subdomains. Try the external form before the backwards-compatible
    268      * internal form.
    269      */
    270 #define LOOKUP_STRATEGY \
    271 	(MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN | \
    272 	(transport_match_parent_style == MATCH_FLAG_PARENT ? \
    273 		MA_FIND_PDMS : MA_FIND_PDDMDS))
    274 
    275     if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0)
    276 	msg_panic("transport_lookup: bad address: \"%s\"", addr);
    277 
    278     if ((value = mail_addr_find_strategy(tp->transport_path, addr, (char **) 0,
    279 					 LOOKUP_STRATEGY)) != 0) {
    280 	parse_transport_entry(value, rcpt_domain, channel, nexthop);
    281 	return (FOUND);
    282     }
    283     if (tp->transport_path->error != 0)
    284 	return (NOTFOUND);
    285 
    286     /*
    287      * Fall back to the wild-card entry.
    288      */
    289     if (tp->wildcard_errno || event_time() > tp->expire)
    290 	transport_wildcard_init(tp);
    291     if (tp->wildcard_errno) {
    292 	tp->transport_path->error = tp->wildcard_errno;
    293 	return (NOTFOUND);
    294     } else if (tp->wildcard_channel) {
    295 	update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop),
    296 		     rcpt_domain, channel, nexthop);
    297 	return (FOUND);
    298     }
    299 
    300     /*
    301      * We really did not find it.
    302      */
    303     return (NOTFOUND);
    304 }
    305 
    306 #ifdef TEST
    307 
    308  /*
    309   * Proof-of-concept test program. Read an address from stdin, and spit out
    310   * the lookup result.
    311   */
    312 
    313 #include <string.h>
    314 
    315 #include <mail_conf.h>
    316 #include <vstream.h>
    317 #include <vstring_vstream.h>
    318 
    319 static NORETURN usage(const char *progname)
    320 {
    321     msg_fatal("usage: %s [-v] database", progname);
    322 }
    323 
    324 int     main(int argc, char **argv)
    325 {
    326     VSTRING *buffer = vstring_alloc(100);
    327     VSTRING *channel = vstring_alloc(100);
    328     VSTRING *nexthop = vstring_alloc(100);
    329     TRANSPORT_INFO *tp;
    330     char   *bp;
    331     char   *addr_field;
    332     char   *rcpt_domain;
    333     char   *expect_channel;
    334     char   *expect_nexthop;
    335     int     status;
    336     int     ch;
    337     int     errs = 0;
    338 
    339     /*
    340      * Parse JCL.
    341      */
    342     while ((ch = GETOPT(argc, argv, "v")) > 0) {
    343 	switch (ch) {
    344 	case 'v':
    345 	    msg_verbose++;
    346 	    break;
    347 	default:
    348 	    usage(argv[0]);
    349 	}
    350     }
    351     if (argc != optind + 1)
    352 	usage(argv[0]);
    353 
    354     /*
    355      * Initialize.
    356      */
    357 #define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0)
    358 
    359     mail_conf_read();				/* XXX eliminate dependency. */
    360     UPDATE(var_rcpt_delim, "+");
    361     UPDATE(var_mydomain, "localdomain");
    362     UPDATE(var_myorigin, "localhost.localdomain");
    363     UPDATE(var_mydest, "localhost.localdomain");
    364 
    365     tp = transport_pre_init("transport map", argv[optind]);
    366     transport_post_init(tp);
    367 
    368     while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
    369 	bp = STR(buffer);
    370 
    371 	/*
    372 	 * Parse the input and expectations. XXX We can't expect empty
    373 	 * fields, so require '-' instead.
    374 	 */
    375 	if ((addr_field = mystrtok(&bp, ":")) == 0)
    376 	    msg_fatal("no address field");
    377 	if ((rcpt_domain = strrchr(addr_field, '@')) == 0)
    378 	    msg_fatal("no recipient domain");
    379 	rcpt_domain += 1;
    380 	expect_channel = mystrtok(&bp, ":");
    381 	expect_nexthop = mystrtok(&bp, ":");
    382 	if ((expect_channel != 0) != (expect_nexthop != 0))
    383 	    msg_fatal("specify both channel and nexthop, or specify neither");
    384 	if (expect_channel) {
    385 	    if (strcmp(expect_channel, "-") == 0)
    386 		*expect_channel = 0;
    387 	    if (strcmp(expect_nexthop, "-") == 0)
    388 		*expect_nexthop = 0;
    389 	    vstring_strcpy(channel, "DEFAULT");
    390 	    vstring_strcpy(nexthop, rcpt_domain);
    391 	}
    392 	if (mystrtok(&bp, ":") != 0)
    393 	    msg_fatal("garbage after nexthop field");
    394 
    395 	/*
    396 	 * Lookups.
    397 	 */
    398 	status = transport_lookup(tp, addr_field, rcpt_domain,
    399 				  channel, nexthop);
    400 
    401 	/*
    402 	 * Enforce expectations.
    403 	 */
    404 	if (expect_nexthop && status) {
    405 	    vstream_printf("%s:%s -> %s:%s \n",
    406 			   addr_field, rcpt_domain,
    407 			   STR(channel), STR(nexthop));
    408 	    vstream_fflush(VSTREAM_OUT);
    409 	    if (strcmp(expect_channel, STR(channel)) != 0) {
    410 		msg_warn("expect channel '%s' but got '%s'",
    411 			 expect_channel, STR(channel));
    412 		errs = 1;
    413 	    }
    414 	    if (strcmp(expect_nexthop, STR(nexthop)) != 0) {
    415 		msg_warn("expect nexthop '%s' but got '%s'",
    416 			 expect_nexthop, STR(nexthop));
    417 		errs = 1;
    418 	    }
    419 	} else if (expect_nexthop && !status) {
    420 	    vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
    421 			   tp->transport_path->error ?
    422 			   "(try again)" : "(not found)");
    423 	    vstream_fflush(VSTREAM_OUT);
    424 	    msg_warn("expect channel '%s' but got none", expect_channel);
    425 	    msg_warn("expect nexthop '%s' but got none", expect_nexthop);
    426 	    errs = 1;
    427 	} else if (!status) {
    428 	    vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
    429 			   tp->transport_path->error ?
    430 			   "(try again)" : "(not found)");
    431 	}
    432     }
    433     transport_free(tp);
    434     vstring_free(nexthop);
    435     vstring_free(channel);
    436     vstring_free(buffer);
    437     exit(errs != 0);
    438 }
    439 
    440 #endif
    441