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