/*++ /* NAME /* transport 3 /* SUMMARY /* transport mapping /* SYNOPSIS /* #include "transport.h" /* /* TRANSPORT_INFO *transport_pre_init(maps_name, maps) /* const char *maps_name; /* const char *maps; /* /* void transport_post_init(info) /* TRANSPORT_INFO *info; /* /* int transport_lookup(info, address, rcpt_domain, channel, nexthop) /* TRANSPORT_INFO *info; /* const char *address; /* const char *rcpt_domain; /* VSTRING *channel; /* VSTRING *nexthop; /* /* void transport_free(info); /* TRANSPORT_INFO * info; /* DESCRIPTION /* This module implements access to the table that maps transport /* user@domain addresses to (channel, nexthop) tuples. /* /* transport_pre_init() performs initializations that should be /* done before the process enters the chroot jail, and /* before calling transport_lookup(). /* /* transport_post_init() can be invoked after entering the chroot /* jail, and must be called before calling transport_lookup(). /* /* transport_lookup() finds the channel and nexthop for the given /* domain, and returns 1 if something was found. Otherwise, 0 /* is returned. /* DIAGNOSTICS /* info->transport_path->error is non-zero when the lookup /* should be tried again. /* SEE ALSO /* maps(3), multi-dictionary search /* strip_addr(3), strip extension from address /* transport(5), format of transport map /* CONFIGURATION PARAMETERS /* transport_maps, names of maps to be searched. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include #include /* Utility library. */ #include #include #include #include #include #include #include /* Global library. */ #include #include #include #include #include /* Application-specific. */ #include "transport.h" static int transport_match_parent_style; #define STR(x) vstring_str(x) static void transport_wildcard_init(TRANSPORT_INFO *); /* transport_pre_init - pre-jail initialization */ TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name, const char *transport_maps) { TRANSPORT_INFO *tp; tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp)); tp->transport_path = maps_create(transport_maps_name, transport_maps, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); tp->wildcard_channel = tp->wildcard_nexthop = 0; tp->wildcard_errno = 0; tp->expire = 0; return (tp); } /* transport_post_init - post-jail initialization */ void transport_post_init(TRANSPORT_INFO *tp) { transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS); transport_wildcard_init(tp); } /* transport_free - destroy transport info */ void transport_free(TRANSPORT_INFO *tp) { if (tp->transport_path) maps_free(tp->transport_path); if (tp->wildcard_channel) vstring_free(tp->wildcard_channel); if (tp->wildcard_nexthop) vstring_free(tp->wildcard_nexthop); myfree((void *) tp); } /* update_entry - update from transport table entry */ static void update_entry(const char *new_channel, const char *new_nexthop, const char *rcpt_domain, VSTRING *channel, VSTRING *nexthop) { /* * :[nexthop] means don't change the channel, and don't change the * nexthop unless a non-default nexthop is specified. Thus, a right-hand * side of ":" is the transport table equivalent of a NOOP. */ if (*new_channel == 0) { /* :[nexthop] */ if (*new_nexthop != 0) vstring_strcpy(nexthop, new_nexthop); } /* * transport[:[nexthop]] means change the channel, and reset the nexthop * to the default unless a non-default nexthop is specified. */ else { vstring_strcpy(channel, new_channel); if (*new_nexthop != 0) vstring_strcpy(nexthop, new_nexthop); else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0 && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0) vstring_strcpy(nexthop, rcpt_domain); else vstring_strcpy(nexthop, "Address is undeliverable"); } } /* parse_transport_entry - parse transport table entry */ static void parse_transport_entry(const char *value, const char *rcpt_domain, VSTRING *channel, VSTRING *nexthop) { char *saved_value; const char *host; #define FOUND 1 #define NOTFOUND 0 /* * It would be great if we could specify a recipient address in the * lookup result. Unfortunately, we cannot simply run the result through * a parser that recognizes "transport:user@domain" because the lookup * result can have arbitrary content (especially in the case of the error * mailer). */ saved_value = mystrdup(value); host = split_at(saved_value, ':'); update_entry(saved_value, host ? host : "", rcpt_domain, channel, nexthop); myfree(saved_value); } /* transport_wildcard_init - (re) initialize wild-card lookup result */ static void transport_wildcard_init(TRANSPORT_INFO *tp) { VSTRING *channel = vstring_alloc(10); VSTRING *nexthop = vstring_alloc(10); const char *value; /* * Both channel and nexthop may be zero-length strings. Therefore we must * use something else to represent "wild-card does not exist". We use * null VSTRING pointers, for historical reasons. */ if (tp->wildcard_channel) vstring_free(tp->wildcard_channel); if (tp->wildcard_nexthop) vstring_free(tp->wildcard_nexthop); /* * Technically, the wildcard lookup pattern is redundant. A static map * (keys always match, result is fixed string) could achieve the same: * * transport_maps = hash:/etc/postfix/transport static:xxx:yyy * * But the user interface of such an approach would be less intuitive. We * tolerate the continued existence of wildcard lookup patterns because * of human interface considerations. */ #define WILDCARD "*" #define FULL 0 #define PARTIAL DICT_FLAG_FIXED if ((value = maps_find(tp->transport_path, WILDCARD, FULL)) != 0) { parse_transport_entry(value, "", channel, nexthop); tp->wildcard_errno = 0; tp->wildcard_channel = channel; tp->wildcard_nexthop = nexthop; if (msg_verbose) msg_info("wildcard_{chan:hop}={%s:%s}", vstring_str(channel), vstring_str(nexthop)); } else { tp->wildcard_errno = tp->transport_path->error; vstring_free(channel); vstring_free(nexthop); tp->wildcard_channel = 0; tp->wildcard_nexthop = 0; } tp->expire = event_time() + 30; /* XXX make configurable */ } /* transport_lookup - map a transport domain */ int transport_lookup(TRANSPORT_INFO *tp, const char *addr, const char *rcpt_domain, VSTRING *channel, VSTRING *nexthop) { char *ratsign = 0; const char *value; #define STREQ(x,y) (strcmp((x), (y)) == 0) #define DISCARD_EXTENSION ((char **) 0) /* * The null recipient is rewritten to the local mailer daemon address. */ if (*addr == 0) { msg_warn("transport_lookup: null address - skipping table lookup"); return (NOTFOUND); } /* * Look up the full and extension-stripped address, then match the domain * and subdomains. Try the external form before the backwards-compatible * internal form. */ #define LOOKUP_STRATEGY \ (MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN | \ (transport_match_parent_style == MATCH_FLAG_PARENT ? \ MA_FIND_PDMS : MA_FIND_PDDMDS)) if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0) msg_panic("transport_lookup: bad address: \"%s\"", addr); if ((value = mail_addr_find_strategy(tp->transport_path, addr, (char **) 0, LOOKUP_STRATEGY)) != 0) { parse_transport_entry(value, rcpt_domain, channel, nexthop); return (FOUND); } if (tp->transport_path->error != 0) return (NOTFOUND); /* * Fall back to the wild-card entry. */ if (tp->wildcard_errno || event_time() > tp->expire) transport_wildcard_init(tp); if (tp->wildcard_errno) { tp->transport_path->error = tp->wildcard_errno; return (NOTFOUND); } else if (tp->wildcard_channel) { update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop), rcpt_domain, channel, nexthop); return (FOUND); } /* * We really did not find it. */ return (NOTFOUND); } #ifdef TEST /* * Proof-of-concept test program. Read an address from stdin, and spit out * the lookup result. */ #include #include #include #include static NORETURN usage(const char *progname) { msg_fatal("usage: %s [-v] database", progname); } int main(int argc, char **argv) { VSTRING *buffer = vstring_alloc(100); VSTRING *channel = vstring_alloc(100); VSTRING *nexthop = vstring_alloc(100); TRANSPORT_INFO *tp; char *bp; char *addr_field; char *rcpt_domain; char *expect_channel; char *expect_nexthop; int status; int ch; int errs = 0; /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "v")) > 0) { switch (ch) { case 'v': msg_verbose++; break; default: usage(argv[0]); } } if (argc != optind + 1) usage(argv[0]); /* * Initialize. */ #define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0) mail_conf_read(); /* XXX eliminate dependency. */ UPDATE(var_rcpt_delim, "+"); UPDATE(var_mydomain, "localdomain"); UPDATE(var_myorigin, "localhost.localdomain"); UPDATE(var_mydest, "localhost.localdomain"); tp = transport_pre_init("transport map", argv[optind]); transport_post_init(tp); while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { bp = STR(buffer); /* * Parse the input and expectations. XXX We can't expect empty * fields, so require '-' instead. */ if ((addr_field = mystrtok(&bp, ":")) == 0) msg_fatal("no address field"); if ((rcpt_domain = strrchr(addr_field, '@')) == 0) msg_fatal("no recipient domain"); rcpt_domain += 1; expect_channel = mystrtok(&bp, ":"); expect_nexthop = mystrtok(&bp, ":"); if ((expect_channel != 0) != (expect_nexthop != 0)) msg_fatal("specify both channel and nexthop, or specify neither"); if (expect_channel) { if (strcmp(expect_channel, "-") == 0) *expect_channel = 0; if (strcmp(expect_nexthop, "-") == 0) *expect_nexthop = 0; vstring_strcpy(channel, "DEFAULT"); vstring_strcpy(nexthop, rcpt_domain); } if (mystrtok(&bp, ":") != 0) msg_fatal("garbage after nexthop field"); /* * Lookups. */ status = transport_lookup(tp, addr_field, rcpt_domain, channel, nexthop); /* * Enforce expectations. */ if (expect_nexthop && status) { vstream_printf("%s:%s -> %s:%s \n", addr_field, rcpt_domain, STR(channel), STR(nexthop)); vstream_fflush(VSTREAM_OUT); if (strcmp(expect_channel, STR(channel)) != 0) { msg_warn("expect channel '%s' but got '%s'", expect_channel, STR(channel)); errs = 1; } if (strcmp(expect_nexthop, STR(nexthop)) != 0) { msg_warn("expect nexthop '%s' but got '%s'", expect_nexthop, STR(nexthop)); errs = 1; } } else if (expect_nexthop && !status) { vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain, tp->transport_path->error ? "(try again)" : "(not found)"); vstream_fflush(VSTREAM_OUT); msg_warn("expect channel '%s' but got none", expect_channel); msg_warn("expect nexthop '%s' but got none", expect_nexthop); errs = 1; } else if (!status) { vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain, tp->transport_path->error ? "(try again)" : "(not found)"); } } transport_free(tp); vstring_free(nexthop); vstring_free(channel); vstring_free(buffer); exit(errs != 0); } #endif