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 93static int transport_match_parent_style; 94 95#define STR(x) vstring_str(x) 96 97static void transport_wildcard_init(TRANSPORT_INFO *); 98 99/* transport_pre_init - pre-jail initialization */ 100 101TRANSPORT_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 119void 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 127void 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 140static 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 173static 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 197static 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 247int 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 319static NORETURN usage(const char *progname) 320{ 321 msg_fatal("usage: %s [-v] database", progname); 322} 323 324int 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