transport.c revision 1.2
1/* $NetBSD: transport.c,v 1.2 2017/02/14 01:16:48 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 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/* The global \fIdict_errno\fR 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 61/* System library. */ 62 63#include <sys_defs.h> 64#include <string.h> 65 66/* Utility library. */ 67 68#include <msg.h> 69#include <stringops.h> 70#include <mymalloc.h> 71#include <vstring.h> 72#include <split_at.h> 73#include <dict.h> 74#include <events.h> 75 76/* Global library. */ 77 78#include <strip_addr.h> 79#include <mail_params.h> 80#include <maps.h> 81#include <match_parent_style.h> 82#include <mail_proto.h> 83 84/* Application-specific. */ 85 86#include "transport.h" 87 88static int transport_match_parent_style; 89 90#define STR(x) vstring_str(x) 91 92static void transport_wildcard_init(TRANSPORT_INFO *); 93 94/* transport_pre_init - pre-jail initialization */ 95 96TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name, 97 const char *transport_maps) 98{ 99 TRANSPORT_INFO *tp; 100 101 tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp)); 102 tp->transport_path = maps_create(transport_maps_name, transport_maps, 103 DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX 104 | DICT_FLAG_NO_REGSUB 105 | DICT_FLAG_UTF8_REQUEST); 106 tp->wildcard_channel = tp->wildcard_nexthop = 0; 107 tp->wildcard_errno = 0; 108 tp->expire = 0; 109 return (tp); 110} 111 112/* transport_post_init - post-jail initialization */ 113 114void transport_post_init(TRANSPORT_INFO *tp) 115{ 116 transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS); 117 transport_wildcard_init(tp); 118} 119 120/* transport_free - destroy transport info */ 121 122void transport_free(TRANSPORT_INFO *tp) 123{ 124 if (tp->transport_path) 125 maps_free(tp->transport_path); 126 if (tp->wildcard_channel) 127 vstring_free(tp->wildcard_channel); 128 if (tp->wildcard_nexthop) 129 vstring_free(tp->wildcard_nexthop); 130 myfree((void *) tp); 131} 132 133/* update_entry - update from transport table entry */ 134 135static void update_entry(const char *new_channel, const char *new_nexthop, 136 const char *rcpt_domain, VSTRING *channel, 137 VSTRING *nexthop) 138{ 139 140 /* 141 * :[nexthop] means don't change the channel, and don't change the 142 * nexthop unless a non-default nexthop is specified. Thus, a right-hand 143 * side of ":" is the transport table equivalent of a NOOP. 144 */ 145 if (*new_channel == 0) { /* :[nexthop] */ 146 if (*new_nexthop != 0) 147 vstring_strcpy(nexthop, new_nexthop); 148 } 149 150 /* 151 * transport[:[nexthop]] means change the channel, and reset the nexthop 152 * to the default unless a non-default nexthop is specified. 153 */ 154 else { 155 vstring_strcpy(channel, new_channel); 156 if (*new_nexthop != 0) 157 vstring_strcpy(nexthop, new_nexthop); 158 else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0 159 && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0) 160 vstring_strcpy(nexthop, rcpt_domain); 161 else 162 vstring_strcpy(nexthop, "Address is undeliverable"); 163 } 164} 165 166/* find_transport_entry - look up and parse transport table entry */ 167 168static int find_transport_entry(TRANSPORT_INFO *tp, const char *key, 169 const char *rcpt_domain, int flags, 170 VSTRING *channel, VSTRING *nexthop) 171{ 172 char *saved_value; 173 const char *host; 174 const char *value; 175 176#define FOUND 1 177#define NOTFOUND 0 178 179 /* 180 * Look up an entry with extreme prejudice. 181 * 182 * XXX Should report lookup failure status to caller instead of aborting. 183 */ 184 if ((value = maps_find(tp->transport_path, key, flags)) == 0) 185 return (NOTFOUND); 186 187 /* 188 * It would be great if we could specify a recipient address in the 189 * lookup result. Unfortunately, we cannot simply run the result through 190 * a parser that recognizes "transport:user@domain" because the lookup 191 * result can have arbitrary content (especially in the case of the error 192 * mailer). 193 */ 194 else { 195 saved_value = mystrdup(value); 196 host = split_at(saved_value, ':'); 197 update_entry(saved_value, host ? host : "", rcpt_domain, 198 channel, nexthop); 199 myfree(saved_value); 200 return (FOUND); 201 } 202} 203 204/* transport_wildcard_init - (re) initialize wild-card lookup result */ 205 206static void transport_wildcard_init(TRANSPORT_INFO *tp) 207{ 208 VSTRING *channel = vstring_alloc(10); 209 VSTRING *nexthop = vstring_alloc(10); 210 211 /* 212 * Both channel and nexthop may be zero-length strings. Therefore we must 213 * use something else to represent "wild-card does not exist". We use 214 * null VSTRING pointers, for historical reasons. 215 */ 216 if (tp->wildcard_channel) 217 vstring_free(tp->wildcard_channel); 218 if (tp->wildcard_nexthop) 219 vstring_free(tp->wildcard_nexthop); 220 221 /* 222 * Technically, the wildcard lookup pattern is redundant. A static map 223 * (keys always match, result is fixed string) could achieve the same: 224 * 225 * transport_maps = hash:/etc/postfix/transport static:xxx:yyy 226 * 227 * But the user interface of such an approach would be less intuitive. We 228 * tolerate the continued existence of wildcard lookup patterns because 229 * of human interface considerations. 230 */ 231#define WILDCARD "*" 232#define FULL 0 233#define PARTIAL DICT_FLAG_FIXED 234 235 if (find_transport_entry(tp, WILDCARD, "", FULL, channel, nexthop)) { 236 tp->wildcard_errno = 0; 237 tp->wildcard_channel = channel; 238 tp->wildcard_nexthop = nexthop; 239 if (msg_verbose) 240 msg_info("wildcard_{chan:hop}={%s:%s}", 241 vstring_str(channel), vstring_str(nexthop)); 242 } else { 243 tp->wildcard_errno = tp->transport_path->error; 244 vstring_free(channel); 245 vstring_free(nexthop); 246 tp->wildcard_channel = 0; 247 tp->wildcard_nexthop = 0; 248 } 249 tp->expire = event_time() + 30; /* XXX make configurable */ 250} 251 252/* transport_lookup - map a transport domain */ 253 254int transport_lookup(TRANSPORT_INFO *tp, const char *addr, 255 const char *rcpt_domain, 256 VSTRING *channel, VSTRING *nexthop) 257{ 258 char *stripped_addr; 259 char *ratsign = 0; 260 const char *name; 261 const char *next; 262 int found; 263 264#define STREQ(x,y) (strcmp((x), (y)) == 0) 265#define DISCARD_EXTENSION ((char **) 0) 266 267 /* 268 * The null recipient is rewritten to the local mailer daemon address. 269 */ 270 if (*addr == 0) { 271 msg_warn("transport_lookup: null address - skipping table lookup"); 272 return (NOTFOUND); 273 } 274 275 /* 276 * Look up the full address with the FULL flag to include regexp maps in 277 * the query. 278 */ 279 if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0) 280 msg_panic("transport_lookup: bad address: \"%s\"", addr); 281 282 if (find_transport_entry(tp, addr, rcpt_domain, FULL, channel, nexthop)) 283 return (FOUND); 284 if (tp->transport_path->error != 0) 285 return (NOTFOUND); 286 287 /* 288 * If the full address did not match, and there is an address extension, 289 * look up the stripped address with the PARTIAL flag to avoid matching 290 * partial lookup keys with regular expressions. 291 */ 292 if ((stripped_addr = strip_addr(addr, DISCARD_EXTENSION, 293 var_rcpt_delim)) != 0) { 294 found = find_transport_entry(tp, stripped_addr, rcpt_domain, PARTIAL, 295 channel, nexthop); 296 297 myfree(stripped_addr); 298 if (found) 299 return (FOUND); 300 if (tp->transport_path->error != 0) 301 return (NOTFOUND); 302 } 303 304 /* 305 * If the full and stripped address lookup fails, try domain name lookup. 306 * 307 * Keep stripping domain components until nothing is left or until a 308 * matching entry is found. 309 * 310 * After checking the full domain name, check for .upper.domain, to 311 * distinguish between the parent domain and it's decendants, a la 312 * sendmail and tcp wrappers. 313 * 314 * Before changing the DB lookup result, make a copy first, in order to 315 * avoid DB cache corruption. 316 * 317 * Specify that the lookup key is partial, to avoid matching partial keys 318 * with regular expressions. 319 */ 320 for (name = ratsign + 1; *name != 0; name = next) { 321 if (find_transport_entry(tp, name, rcpt_domain, PARTIAL, channel, nexthop)) 322 return (FOUND); 323 if (tp->transport_path->error != 0) 324 return (NOTFOUND); 325 if ((next = strchr(name + 1, '.')) == 0) 326 break; 327 if (transport_match_parent_style == MATCH_FLAG_PARENT) 328 next++; 329 } 330 331 /* 332 * Fall back to the wild-card entry. 333 */ 334 if (tp->wildcard_errno || event_time() > tp->expire) 335 transport_wildcard_init(tp); 336 if (tp->wildcard_errno) { 337 tp->transport_path->error = tp->wildcard_errno; 338 return (NOTFOUND); 339 } else if (tp->wildcard_channel) { 340 update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop), 341 rcpt_domain, channel, nexthop); 342 return (FOUND); 343 } 344 345 /* 346 * We really did not find it. 347 */ 348 return (NOTFOUND); 349} 350