int dmarc_init() { int *netmask = NULL; /* Ignored */ int is_ipv6 = 0; char *tld_file = (dmarc_tld_file == NULL) ? "/etc/exim/opendmarc.tlds" : (char *)dmarc_tld_file; /* Set some sane defaults. Also clears previous results when * multiple messages in one connection. */ dmarc_pctx = NULL; dmarc_status = US"none"; dmarc_abort = FALSE; dmarc_pass_fail = US"skipped"; dmarc_used_domain = US""; dmarc_ar_header = NULL; dmarc_has_been_checked = FALSE; header_from_sender = NULL; spf_sender_domain = NULL; spf_human_readable = NULL; /* ACLs have "control=dmarc_disable_verify" */ if (dmarc_disable_verify == TRUE) return OK; (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx); dmarc_ctx.nscount = 0; libdm_status = opendmarc_policy_library_init(&dmarc_ctx); if (libdm_status != DMARC_PARSE_OKAY) { log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s", opendmarc_policy_status_to_str(libdm_status)); dmarc_abort = TRUE; } if (dmarc_tld_file == NULL) dmarc_abort = TRUE; else if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL)) { log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d", tld_file, errno); dmarc_abort = TRUE; } if (sender_host_address == NULL) dmarc_abort = TRUE; /* This catches locally originated email and startup errors above. */ if (!dmarc_abort) { is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6; dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6); if (dmarc_pctx == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure creating policy context: ip=%s", sender_host_address); dmarc_abort = TRUE; } } return OK; }
int iplsearch_find(void *handle, uschar *filename, uschar *keystring, int length, uschar **result, uschar **errmsg, BOOL *do_cache) { do_cache = do_cache; /* Keep picky compilers happy */ if ((length == 1 && keystring[0] == '*') || string_is_ip_address(keystring, NULL) != 0) { return internal_lsearch_find(handle, filename, keystring, length, result, errmsg, LSEARCH_IP); } else { *errmsg = string_sprintf("\"%s\" is not a valid iplsearch key (an IP " "address, with optional CIDR mask, is wanted): " "in a host list, use net-iplsearch as the search type", keystring); return DEFER; } }
int dnslookup_router_entry( router_instance *rblock, /* data for this instantiation */ address_item *addr, /* address we are working on */ struct passwd *pw, /* passwd entry after check_local_user */ int verify, /* v_none/v_recipient/v_sender/v_expn */ address_item **addr_local, /* add it to this if it's local */ address_item **addr_remote, /* add it to this if it's remote */ address_item **addr_new, /* put new addresses on here */ address_item **addr_succeed) /* put old address here on success */ { host_item h; int rc; int widen_sep = 0; int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A; dnslookup_router_options_block *ob = (dnslookup_router_options_block *)(rblock->options_block); uschar *srv_service = NULL; uschar *widen = NULL; uschar *pre_widen = addr->domain; uschar *post_widen = NULL; uschar *fully_qualified_name; uschar *listptr; uschar widen_buffer[256]; addr_new = addr_new; /* Keep picky compilers happy */ addr_succeed = addr_succeed; DEBUG(D_route) debug_printf("%s router called for %s\n domain = %s\n", rblock->name, addr->address, addr->domain); /* If an SRV check is required, expand the service name */ if (ob->check_srv != NULL) { srv_service = expand_string(ob->check_srv); if (srv_service == NULL && !expand_string_forcedfail) { addr->message = string_sprintf("%s router: failed to expand \"%s\": %s", rblock->name, ob->check_srv, expand_string_message); return DEFER; } else whichrrs |= HOST_FIND_BY_SRV; } /* Set up the first of any widening domains. The code further down copes with either pre- or post-widening, but at present there is no way to turn on pre-widening, as actually doing so seems like a rather bad idea, and nobody has requested it. Pre-widening would cause local abbreviated names to take precedence over global names. For example, if the domain is "xxx.ch" it might be something in the "ch" toplevel domain, but it also might be xxx.ch.xyz.com. The choice of pre- or post-widening affects which takes precedence. If ever somebody comes up with some kind of requirement for pre-widening, presumably with some conditions under which it is done, it can be selected here. The rewrite_headers option works only when routing an address at transport time, because the alterations to the headers are not persistent so must be worked out immediately before they are used. Sender addresses are routed for verification purposes, but never at transport time, so any header changes that you might expect as a result of sender domain widening do not occur. Therefore we do not perform widening when verifying sender addresses; however, widening sender addresses is OK if we do not have to rewrite the headers. A corollary of this is that if the current address is not the original address, then it does not appear in the message header so it is also OK to widen. The suppression of widening for sender addresses is silent because it is the normal desirable behaviour. */ if (ob->widen_domains != NULL && (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL)) { listptr = ob->widen_domains; widen = string_nextinlist(&listptr, &widen_sep, widen_buffer, sizeof(widen_buffer)); /**** if (some condition requiring pre-widening) { post_widen = pre_widen; pre_widen = NULL; } ****/ } /* Loop to cope with explicit widening of domains as configured. This code copes with widening that may happen before or after the original name. The decision as to which is taken above. */ for (;;) { int flags = whichrrs; BOOL removed = FALSE; if (pre_widen != NULL) { h.name = pre_widen; pre_widen = NULL; } else if (widen != NULL) { h.name = string_sprintf("%s.%s", addr->domain, widen); widen = string_nextinlist(&listptr, &widen_sep, widen_buffer, sizeof(widen_buffer)); DEBUG(D_route) debug_printf("%s router widened %s to %s\n", rblock->name, addr->domain, h.name); } else if (post_widen != NULL) { h.name = post_widen; post_widen = NULL; DEBUG(D_route) debug_printf("%s router trying %s after widening failed\n", rblock->name, h.name); } else return DECLINE; /* Set up the rest of the initial host item. Others may get chained on if there is more than one IP address. We set it up here instead of outside the loop so as to re-initialize if a previous try succeeded but was rejected because of not having an MX record. */ h.next = NULL; h.address = NULL; h.port = PORT_NONE; h.mx = MX_NONE; h.status = hstatus_unknown; h.why = hwhy_unknown; h.last_try = 0; /* Unfortunately, we cannot set the mx_only option in advance, because the DNS lookup may extend an unqualified name. Therefore, we must do the test subsequently. We use the same logic as that for widen_domains above to avoid requesting a header rewrite that cannot work. */ if (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL) { if (ob->qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; if (ob->search_parents) flags |= HOST_FIND_SEARCH_PARENTS; } rc = host_find_bydns(&h, rblock->ignore_target_hosts, flags, srv_service, ob->srv_fail_domains, ob->mx_fail_domains, &fully_qualified_name, &removed); if (removed) setflag(addr, af_local_host_removed); /* If host found with only address records, test for the domain's being in the mx_domains list. Note that this applies also to SRV records; the name of the option is historical. */ if ((rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) && h.mx < 0 && ob->mx_domains != NULL) { switch(match_isinlist(fully_qualified_name, &(ob->mx_domains), 0, &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL)) { case DEFER: addr->message = US"lookup defer for mx_domains"; return DEFER; case OK: DEBUG(D_route) debug_printf("%s router rejected %s: no MX record(s)\n", rblock->name, fully_qualified_name); continue; } } /* Deferral returns forthwith, and anything other than failure breaks the loop. */ if (rc == HOST_FIND_AGAIN) { if (rblock->pass_on_timeout) { DEBUG(D_route) debug_printf("%s router timed out, and pass_on_timeout is set\n", rblock->name); return PASS; } addr->message = US"host lookup did not complete"; return DEFER; } if (rc != HOST_FIND_FAILED) break; /* Check to see if the failure is the result of MX records pointing to non-existent domains, and if so, set an appropriate error message; the case of an MX or SRV record pointing to "." is another special case that we can detect. Otherwise "unknown mail domain" is used, which is confusing. Also, in this case don't do the widening. We need check only the first host to see if its MX has been filled in, but there is no address, because if there were any usable addresses returned, we would not have had HOST_FIND_FAILED. As a common cause of this problem is MX records with IP addresses on the RHS, give a special message in this case. */ if (h.mx >= 0 && h.address == NULL) { setflag(addr, af_pass_message); /* This is not a security risk */ if (h.name[0] == 0) addr->message = US"an MX or SRV record indicated no SMTP service"; else { addr->message = US"all relevant MX records point to non-existent hosts"; if (!allow_mx_to_ip && string_is_ip_address(h.name, NULL) != 0) { addr->user_message = string_sprintf("It appears that the DNS operator for %s\n" "has installed an invalid MX record with an IP address\n" "instead of a domain name on the right hand side.", addr->domain); addr->message = string_sprintf("%s or (invalidly) to IP addresses", addr->message); } } return DECLINE; } /* If there's a syntax error, do not continue with any widening, and note the error. */ if (host_find_failed_syntax) { addr->message = string_sprintf("mail domain \"%s\" is syntactically " "invalid", h.name); return DECLINE; } } /* If the original domain name has been changed as a result of the host lookup, set up a child address for rerouting and request header rewrites if so configured. Then yield REROUTED. However, if the only change is a change of case in the domain name, which some resolvers yield (others don't), just change the domain name in the original address so that the official version is used in RCPT commands. */ if (Ustrcmp(addr->domain, fully_qualified_name) != 0) { if (strcmpic(addr->domain, fully_qualified_name) == 0) { uschar *at = Ustrrchr(addr->address, '@'); memcpy(at+1, fully_qualified_name, Ustrlen(at+1)); } else { rf_change_domain(addr, fully_qualified_name, ob->rewrite_headers, addr_new); return REROUTED; } } /* If the yield is HOST_FOUND_LOCAL, the remote domain name either found MX records with the lowest numbered one pointing to a host with an IP address that is set on one of the interfaces of this machine, or found A records or got addresses from gethostbyname() that contain one for this machine. This can happen quite legitimately if the original name was a shortened form of a domain, but we will have picked that up already via the name change test above. Otherwise, the action to be taken can be configured by the self option, the handling of which is in a separate function, as it is also required for other routers. */ if (rc == HOST_FOUND_LOCAL) { rc = rf_self_action(addr, &h, rblock->self_code, rblock->self_rewrite, rblock->self, addr_new); if (rc != OK) return rc; } /* Otherwise, insist on being a secondary MX if so configured */ else if (ob->check_secondary_mx && !testflag(addr, af_local_host_removed)) { DEBUG(D_route) debug_printf("check_secondary_mx set and local host not secondary\n"); return DECLINE; } /* Set up the errors address, if any. */ rc = rf_get_errors_address(addr, rblock, verify, &(addr->p.errors_address)); if (rc != OK) return rc; /* Set up the additional and removeable headers for this address. */ rc = rf_get_munge_headers(addr, rblock, &(addr->p.extra_headers), &(addr->p.remove_headers)); if (rc != OK) return rc; /* Get store in which to preserve the original host item, chained on to the address. */ addr->host_list = store_get(sizeof(host_item)); addr->host_list[0] = h; /* Fill in the transport and queue the address for delivery. */ if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr, rblock->name, NULL)) return DEFER; addr->transport = rblock->transport; return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER; }
int dns_basic_lookup(dns_answer *dnsa, const uschar *name, int type) { #ifndef STAND_ALONE int rc = -1; const uschar *save_domain; #endif res_state resp = os_get_dns_resolver_res(); tree_node *previous; uschar node_name[290]; /* DNS lookup failures of any kind are cached in a tree. This is mainly so that a timeout on one domain doesn't happen time and time again for messages that have many addresses in the same domain. We rely on the resolver and name server caching for successful lookups. */ sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type), resp->options); previous = tree_search(tree_dns_fails, node_name); if (previous != NULL) { DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n", name, dns_text_type(type), (previous->data.val == DNS_NOMATCH)? "DNS_NOMATCH" : (previous->data.val == DNS_NODATA)? "DNS_NODATA" : (previous->data.val == DNS_AGAIN)? "DNS_AGAIN" : (previous->data.val == DNS_FAIL)? "DNS_FAIL" : "??"); return previous->data.val; } #ifdef EXPERIMENTAL_INTERNATIONAL /* Convert all names to a-label form before doing lookup */ { uschar * alabel; uschar * errstr = NULL; DEBUG(D_dns) if (string_is_utf8(name)) debug_printf("convert utf8 '%s' to alabel for for lookup\n", name); if ((alabel = string_domain_utf8_to_alabel(name, &errstr)), errstr) { DEBUG(D_dns) debug_printf("DNS name '%s' utf8 conversion to alabel failed: %s\n", name, errstr); host_find_failed_syntax = TRUE; return DNS_NOMATCH; } name = alabel; } #endif /* If configured, check the hygene of the name passed to lookup. Otherwise, although DNS lookups may give REFUSED at the lower level, some resolvers turn this into TRY_AGAIN, which is silly. Give a NOMATCH return, since such domains cannot be in the DNS. The check is now done by a regular expression; give it space for substring storage to save it having to get its own if the regex has substrings that are used - the default uses a conditional. This test is omitted for PTR records. These occur only in calls from the dnsdb lookup, which constructs the names itself, so they should be OK. Besides, bitstring labels don't conform to normal name syntax. (But the aren't used any more.) For SRV records, we omit the initial _smtp._tcp. components at the start. */ #ifndef STAND_ALONE /* Omit this for stand-alone tests */ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT) { const uschar *checkname = name; int ovector[3*(EXPAND_MAXN+1)]; dns_pattern_init(); /* For an SRV lookup, skip over the first two components (the service and protocol names, which both start with an underscore). */ if (type == T_SRV || type == T_TLSA) { while (*checkname++ != '.'); while (*checkname++ != '.'); } if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname), 0, PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)) < 0) { DEBUG(D_dns) debug_printf("DNS name syntax check failed: %s (%s)\n", name, dns_text_type(type)); host_find_failed_syntax = TRUE; return DNS_NOMATCH; } } #endif /* STAND_ALONE */ /* Call the resolver; for an overlong response, res_search() will return the number of bytes the message would need, so we need to check for this case. The effect is to truncate overlong data. On some systems, res_search() will recognize "A-for-A" queries and return the IP address instead of returning -1 with h_error=HOST_NOT_FOUND. Some nameservers are also believed to do this. It is, of course, contrary to the specification of the DNS, so we lock it out. */ if ((type == T_A || type == T_AAAA) && string_is_ip_address(name, NULL) != 0) return DNS_NOMATCH; /* If we are running in the test harness, instead of calling the normal resolver (res_search), we call fakens_search(), which recognizes certain special domains, and interfaces to a fake nameserver for certain special zones. */ dnsa->answerlen = running_in_test_harness ? fakens_search(name, type, dnsa->answer, MAXPACKET) : res_search(CCS name, C_IN, type, dnsa->answer, MAXPACKET); if (dnsa->answerlen > MAXPACKET) { DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) resulted in overlong packet (size %d), truncating to %d.\n", name, dns_text_type(type), dnsa->answerlen, MAXPACKET); dnsa->answerlen = MAXPACKET; } if (dnsa->answerlen < 0) switch (h_errno) { case HOST_NOT_FOUND: DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n" "returning DNS_NOMATCH\n", name, dns_text_type(type)); return dns_return(name, type, DNS_NOMATCH); case TRY_AGAIN: DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n", name, dns_text_type(type)); /* Cut this out for various test programs */ #ifndef STAND_ALONE save_domain = deliver_domain; deliver_domain = string_copy(name); /* set $domain */ rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL); deliver_domain = save_domain; if (rc != OK) { DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n"); return dns_return(name, type, DNS_AGAIN); } DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning " "DNS_NOMATCH\n", name); return dns_return(name, type, DNS_NOMATCH); #else /* For stand-alone tests */ return dns_return(name, type, DNS_AGAIN); #endif case NO_RECOVERY: DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n" "returning DNS_FAIL\n", name, dns_text_type(type)); return dns_return(name, type, DNS_FAIL); case NO_DATA: DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n" "returning DNS_NODATA\n", name, dns_text_type(type)); return dns_return(name, type, DNS_NODATA); default: DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n" "returning DNS_FAIL\n", name, dns_text_type(type), h_errno); return dns_return(name, type, DNS_FAIL); } DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n", name, dns_text_type(type)); return DNS_SUCCEED; }
static int dnsdb_find(void *handle, uschar *filename, const uschar *keystring, int length, uschar **result, uschar **errmsg, uint *do_cache) { int rc; int size = 256; int ptr = 0; int sep = 0; int defer_mode = PASS; int dnssec_mode = OK; int save_retrans = dns_retrans; int save_retry = dns_retry; int type; int failrc = FAIL; const uschar *outsep = CUS"\n"; const uschar *outsep2 = NULL; uschar *equals, *domain, *found; /* Because we're the working in the search pool, we try to reclaim as much store as possible later, so we preallocate the result here */ uschar *yield = store_get(size); dns_record *rr; dns_answer dnsa; dns_scan dnss; handle = handle; /* Keep picky compilers happy */ filename = filename; length = length; do_cache = do_cache; /* If the string starts with '>' we change the output separator. If it's followed by ';' or ',' we set the TXT output separator. */ while (isspace(*keystring)) keystring++; if (*keystring == '>') { outsep = keystring + 1; keystring += 2; if (*keystring == ',') { outsep2 = keystring + 1; keystring += 2; } else if (*keystring == ';') { outsep2 = US""; keystring++; } while (isspace(*keystring)) keystring++; } /* Check for a modifier keyword. */ for (;;) { if (strncmpic(keystring, US"defer_", 6) == 0) { keystring += 6; if (strncmpic(keystring, US"strict", 6) == 0) { defer_mode = DEFER; keystring += 6; } else if (strncmpic(keystring, US"lax", 3) == 0) { defer_mode = PASS; keystring += 3; } else if (strncmpic(keystring, US"never", 5) == 0) { defer_mode = OK; keystring += 5; } else { *errmsg = US"unsupported dnsdb defer behaviour"; return DEFER; } } else if (strncmpic(keystring, US"dnssec_", 7) == 0) { keystring += 7; if (strncmpic(keystring, US"strict", 6) == 0) { dnssec_mode = DEFER; keystring += 6; } else if (strncmpic(keystring, US"lax", 3) == 0) { dnssec_mode = PASS; keystring += 3; } else if (strncmpic(keystring, US"never", 5) == 0) { dnssec_mode = OK; keystring += 5; } else { *errmsg = US"unsupported dnsdb dnssec behaviour"; return DEFER; } } else if (strncmpic(keystring, US"retrans_", 8) == 0) { int timeout_sec; if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0) { *errmsg = US"unsupported dnsdb timeout value"; return DEFER; } dns_retrans = timeout_sec; while (*keystring != ',') keystring++; } else if (strncmpic(keystring, US"retry_", 6) == 0) { int retries; if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0) { *errmsg = US"unsupported dnsdb retry count"; return DEFER; } dns_retry = retries; } else break; while (isspace(*keystring)) keystring++; if (*keystring++ != ',') { *errmsg = US"dnsdb modifier syntax error"; return DEFER; } while (isspace(*keystring)) keystring++; } /* Figure out the "type" value if it is not T_TXT. If the keystring contains an = this must be preceded by a valid type name. */ type = T_TXT; if ((equals = Ustrchr(keystring, '=')) != NULL) { int i, len; uschar *tend = equals; while (tend > keystring && isspace(tend[-1])) tend--; len = tend - keystring; for (i = 0; i < nelem(type_names); i++) if (len == Ustrlen(type_names[i]) && strncmpic(keystring, US type_names[i], len) == 0) { type = type_values[i]; break; } if (i >= nelem(type_names)) { *errmsg = US"unsupported DNS record type"; return DEFER; } keystring = equals + 1; while (isspace(*keystring)) keystring++; } /* Initialize the resolver in case this is the first time it has been used. */ dns_init(FALSE, FALSE, dnssec_mode != OK); /* The remainder of the string must be a list of domains. As long as the lookup for at least one of them succeeds, we return success. Failure means that none of them were found. The original implementation did not support a list of domains. Adding the list feature is compatible, except in one case: when PTR records are being looked up for a single IPv6 address. Fortunately, we can hack in a compatibility feature here: If the type is PTR and no list separator is specified, and the entire remaining string is valid as an IP address, set an impossible separator so that it is treated as one item. */ if (type == T_PTR && keystring[0] != '<' && string_is_ip_address(keystring, NULL) != 0) sep = -1; /* SPF strings should be concatenated without a separator, thus make it the default if not defined (see RFC 4408 section 3.1.3). Multiple SPF records are forbidden (section 3.1.2) but are currently not handled specially, thus they are concatenated with \n by default. MX priority and value are space-separated by default. SRV and TLSA record parts are space-separated by default. */ if (!outsep2) switch(type) { case T_SPF: outsep2 = US""; break; case T_SRV: case T_MX: case T_TLSA: outsep2 = US" "; break; } /* Now scan the list and do a lookup for each item */ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) { uschar rbuffer[256]; int searchtype = (type == T_CSA)? T_SRV : /* record type we want */ (type == T_MXH)? T_MX : (type == T_ZNS)? T_NS : type; /* If the type is PTR or CSA, we have to construct the relevant magic lookup key if the original is an IP address (some experimental protocols are using PTR records for different purposes where the key string is a host name, and Exim's extended CSA can be keyed by domains or IP addresses). This code for doing the reversal is now in a separate function. */ if ((type == T_PTR || type == T_CSA) && string_is_ip_address(domain, NULL) != 0) { dns_build_reverse(domain, rbuffer); domain = rbuffer; } do { DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain); /* Do the lookup and sort out the result. There are four special types that are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH. The first two are handled in a special lookup function so that the facility could be used from other parts of the Exim code. T_ADDRESSES is handled by looping over the types of A lookup. T_MXH affects only what happens later on in this function, but for tidiness it is handled by the "special". If the lookup fails, continue with the next domain. In the case of DEFER, adjust the final "nothing found" result, but carry on to the next domain. */ found = domain; #if HAVE_IPV6 if (type == T_ADDRESSES) /* NB cannot happen unless HAVE_IPV6 */ { if (searchtype == T_ADDRESSES) searchtype = T_AAAA; else if (searchtype == T_AAAA) searchtype = T_A; rc = dns_special_lookup(&dnsa, domain, searchtype, CUSS &found); } else #endif rc = dns_special_lookup(&dnsa, domain, type, CUSS &found); lookup_dnssec_authenticated = dnssec_mode==OK ? NULL : dns_is_secure(&dnsa) ? US"yes" : US"no"; if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue; if ( rc != DNS_SUCCEED || (dnssec_mode == DEFER && !dns_is_secure(&dnsa)) ) { if (defer_mode == DEFER) { dns_retrans = save_retrans; dns_retry = save_retry; dns_init(FALSE, FALSE, FALSE); /* clr dnssec bit */ return DEFER; /* always defer */ } if (defer_mode == PASS) failrc = DEFER; /* defer only if all do */ continue; /* treat defer as fail */ } /* Search the returned records */ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr != NULL; rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) { if (rr->type != searchtype) continue; if (*do_cache > rr->ttl) *do_cache = rr->ttl; if (type == T_A || type == T_AAAA || type == T_ADDRESSES) { dns_address *da; for (da = dns_address_from_rr(&dnsa, rr); da; da = da->next) { if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1); yield = string_cat(yield, &size, &ptr, da->address); } continue; } /* Other kinds of record just have one piece of data each, but there may be several of them, of course. */ if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1); if (type == T_TXT || type == T_SPF) { if (outsep2 == NULL) { /* output only the first item of data */ yield = string_catn(yield, &size, &ptr, (uschar *)(rr->data+1), (rr->data)[0]); } else { /* output all items */ int data_offset = 0; while (data_offset < rr->size) { uschar chunk_len = (rr->data)[data_offset++]; if (outsep2[0] != '\0' && data_offset != 1) yield = string_catn(yield, &size, &ptr, outsep2, 1); yield = string_catn(yield, &size, &ptr, US ((rr->data)+data_offset), chunk_len); data_offset += chunk_len; } } } else if (type == T_TLSA) { uint8_t usage, selector, matching_type; uint16_t i, payload_length; uschar s[MAX_TLSA_EXPANDED_SIZE]; uschar * sp = s; uschar * p = US rr->data; usage = *p++; selector = *p++; matching_type = *p++; /* What's left after removing the first 3 bytes above */ payload_length = rr->size - 3; sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2, selector, *outsep2, matching_type, *outsep2); /* Now append the cert/identifier, one hex char at a time */ for (i=0; i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4); i++) sp += sprintf(CS sp, "%02x", (unsigned char)p[i]); yield = string_cat(yield, &size, &ptr, s); } else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */ { int priority, weight, port; uschar s[264]; uschar * p = US rr->data; switch (type) { case T_MXH: /* mxh ignores the priority number and includes only the hostnames */ GETSHORT(priority, p); break; case T_MX: GETSHORT(priority, p); sprintf(CS s, "%d%c", priority, *outsep2); yield = string_cat(yield, &size, &ptr, s); break; case T_SRV: GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2, weight, *outsep2, port, *outsep2); yield = string_cat(yield, &size, &ptr, s); break; case T_CSA: /* See acl_verify_csa() for more comments about CSA. */ GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); if (priority != 1) continue; /* CSA version must be 1 */ /* If the CSA record we found is not the one we asked for, analyse the subdomain assertions in the port field, else analyse the direct authorization status in the weight field. */ if (Ustrcmp(found, domain) != 0) { if (port & 1) *s = 'X'; /* explicit authorization required */ else *s = '?'; /* no subdomain assertions here */ } else { if (weight < 2) *s = 'N'; /* not authorized */ else if (weight == 2) *s = 'Y'; /* authorized */ else if (weight == 3) *s = '?'; /* unauthorizable */ else continue; /* invalid */ } s[1] = ' '; yield = string_catn(yield, &size, &ptr, s, 2); break; default: break; } /* GETSHORT() has advanced the pointer to the target domain. */ rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p, (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); /* If an overlong response was received, the data will have been truncated and dn_expand may fail. */ if (rc < 0) { log_write(0, LOG_MAIN, "host name alias list truncated: type=%s " "domain=%s", dns_text_type(type), domain); break; } else yield = string_cat(yield, &size, &ptr, s); if (type == T_SOA && outsep2 != NULL) { unsigned long serial, refresh, retry, expire, minimum; p += rc; yield = string_catn(yield, &size, &ptr, outsep2, 1); rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p, (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); if (rc < 0) { log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s " "domain=%s", dns_text_type(type), domain); break; } else yield = string_cat(yield, &size, &ptr, s); p += rc; GETLONG(serial, p); GETLONG(refresh, p); GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu", *outsep2, serial, *outsep2, refresh, *outsep2, retry, *outsep2, expire, *outsep2, minimum); yield = string_cat(yield, &size, &ptr, s); } } } /* Loop for list of returned records */ /* Loop for set of A-lookup types */ } while (type == T_ADDRESSES && searchtype != T_A); } /* Loop for list of domains */ /* Reclaim unused memory */ store_reset(yield + ptr + 1); /* If ptr == 0 we have not found anything. Otherwise, insert the terminating zero and return the result. */ dns_retrans = save_retrans; dns_retry = save_retry; dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ if (ptr == 0) return failrc; yield[ptr] = 0; *result = yield; return OK; }
int rf_lookup_hostlist(router_instance *rblock, address_item *addr, uschar *ignore_target_hosts, int lookup_type, int hff_code, address_item **addr_new) { BOOL self_send = FALSE; /* Look up each host address. A lookup may add additional items into the chain if there are multiple addresses. Hence the use of next_h to start each cycle of the loop at the next original host. If any host is identified as being the local host, omit it and any subsequent hosts - i.e. treat the list like an ordered list of MX hosts. If the first host is the local host, act according to the "self" option in the configuration. */ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) { const uschar *canonical_name; int rc, len, port, mx, sort_key; next_h = h->next; if (h->address) { prev = h; continue; } DEBUG(D_route|D_host_lookup) debug_printf("finding IP address for %s\n", h->name); /* Handle any port setting that may be on the name; it will be removed from the end of the name. */ port = host_item_get_port(h); /* Store the previous mx and sort_key values, which were assigned in host_build_hostlist and will be overwritten by host_find_bydns. */ mx = h->mx; sort_key = h->sort_key; /* If the name ends with "/MX", we interpret it to mean "the list of hosts pointed to by MX records with this name", and the MX record values override the ordering from host_build_hostlist. */ len = Ustrlen(h->name); if (len > 3 && strcmpic(h->name + len - 3, US"/mx") == 0) { int whichrrs = lookup_type & LK_IPV4_ONLY ? HOST_FIND_BY_MX | HOST_FIND_IPV4_ONLY : lookup_type & LK_IPV4_PREFER ? HOST_FIND_BY_MX | HOST_FIND_IPV4_FIRST : HOST_FIND_BY_MX; DEBUG(D_route|D_host_lookup) debug_printf("doing DNS MX lookup for %s\n", h->name); mx = MX_NONE; h->name = string_copyn(h->name, len - 3); rc = host_find_bydns(h, ignore_target_hosts, whichrrs, /* look only for MX records */ NULL, /* SRV service not relevant */ NULL, /* failing srv domains not relevant */ NULL, /* no special mx failing domains */ &rblock->dnssec, /* dnssec request/require */ NULL, /* fully_qualified_name */ NULL); /* indicate local host removed */ } /* If explicitly configured to look up by name, or if the "host name" is actually an IP address, do a byname lookup. */ else if (lookup_type & LK_BYNAME || string_is_ip_address(h->name, NULL) != 0) { DEBUG(D_route|D_host_lookup) debug_printf("calling host_find_byname\n"); rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE, &canonical_name, TRUE); } /* Otherwise, do a DNS lookup. If that yields "host not found", and the lookup type is the default (i.e. "bydns" is not explicitly configured), follow up with a byname lookup, just in case. */ else { BOOL removed; int whichrrs = lookup_type & LK_IPV4_ONLY ? HOST_FIND_BY_A : lookup_type & LK_IPV4_PREFER ? HOST_FIND_BY_A | HOST_FIND_BY_AAAA | HOST_FIND_IPV4_FIRST : HOST_FIND_BY_A | HOST_FIND_BY_AAAA; DEBUG(D_route|D_host_lookup) debug_printf("doing DNS lookup\n"); switch (rc = host_find_bydns(h, ignore_target_hosts, whichrrs, NULL, NULL, NULL, &rblock->dnssec, /* domains for request/require */ &canonical_name, &removed)) { case HOST_FOUND: if (removed) setflag(addr, af_local_host_removed); break; case HOST_FIND_FAILED: if (lookup_type & LK_DEFAULT) { DEBUG(D_route|D_host_lookup) debug_printf("DNS lookup failed: trying getipnodebyname\n"); rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE, &canonical_name, TRUE); } break; } } /* Temporary failure defers, unless pass_on_timeout is set */ if (rc == HOST_FIND_SECURITY) { addr->message = string_sprintf("host lookup for %s done insecurely" , h->name); addr->basic_errno = ERRNO_DNSDEFER; return DEFER; } if (rc == HOST_FIND_AGAIN) { if (rblock->pass_on_timeout) { DEBUG(D_route) debug_printf("%s router timed out and pass_on_timeout set\n", rblock->name); return PASS; } addr->message = string_sprintf("host lookup for %s did not complete " "(DNS timeout?)", h->name); addr->basic_errno = ERRNO_DNSDEFER; return DEFER; } /* Permanent failure is controlled by host_find_failed */ if (rc == HOST_FIND_FAILED) { if (hff_code == hff_ignore) { if (prev == NULL) addr->host_list = next_h; else prev->next = next_h; continue; /* With the next host, leave prev unchanged */ } if (hff_code == hff_pass) return PASS; if (hff_code == hff_decline) return DECLINE; addr->basic_errno = ERRNO_UNKNOWNHOST; addr->message = string_sprintf("lookup of host \"%s\" failed in %s router%s", h->name, rblock->name, f.host_find_failed_syntax? ": syntax error in name" : ""); if (hff_code == hff_defer) return DEFER; if (hff_code == hff_fail) return FAIL; addr->special_action = SPECIAL_FREEZE; return DEFER; } /* Deal with the settings that were previously cleared: port, mx and sort_key. */ if (port != PORT_NONE) for (host_item * hh = h; hh != next_h; hh = hh->next) hh->port = port; if (mx != MX_NONE) for (host_item * hh = h; hh != next_h; hh = hh->next) { hh->mx = mx; hh->sort_key = sort_key; } /* A local host gets chopped, with its successors, if there are previous hosts. Otherwise the self option is used. If it is set to "send", any subsequent hosts that are also the local host do NOT get chopped. */ if (rc == HOST_FOUND_LOCAL && !self_send) { if (prev) { DEBUG(D_route) { debug_printf("Removed from host list:\n"); for (; h; h = h->next) debug_printf(" %s\n", h->name); } prev->next = NULL; setflag(addr, af_local_host_removed); break; } rc = rf_self_action(addr, h, rblock->self_code, rblock->self_rewrite, rblock->self, addr_new); if (rc != OK) { addr->host_list = NULL; /* Kill the host list for */ return rc; /* anything other than "send" */ } self_send = TRUE; } /* Ensure that prev is the host before next_h; this will not be h if a lookup found multiple addresses or multiple MX records. */ prev = h; while (prev->next != next_h) prev = prev->next; } return OK; }
/* Create a socket and connect to host (name or number, ipv6 ok) at one of port-range. Arguments: type SOCK_DGRAM or SOCK_STREAM af AF_INET6 or AF_INET for the socket type address the remote address, in text form portlo,porthi the remote port range timeout a timeout connhost if not NULL, host_item filled in with connection details errstr pointer for allocated string on error Return: socket fd, or -1 on failure (having allocated an error string) */ int ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi, int timeout, host_item * connhost, uschar ** errstr) { int namelen, port; host_item shost; host_item *h; int af = 0, fd, fd4 = -1, fd6 = -1; shost.next = NULL; shost.address = NULL; shost.port = portlo; shost.mx = -1; namelen = Ustrlen(hostname); /* Anything enclosed in [] must be an IP address. */ if (hostname[0] == '[' && hostname[namelen - 1] == ']') { uschar * host = string_copy(hostname); host[namelen - 1] = 0; host++; if (string_is_ip_address(host, NULL) == 0) { *errstr = string_sprintf("malformed IP address \"%s\"", hostname); return -1; } shost.name = shost.address = host; } /* Otherwise check for an unadorned IP address */ else if (string_is_ip_address(hostname, NULL) != 0) shost.name = shost.address = string_copy(hostname); /* Otherwise lookup IP address(es) from the name */ else { shost.name = string_copy(hostname); if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE) != HOST_FOUND) { *errstr = string_sprintf("no IP address found for host %s", shost.name); return -1; } } /* Try to connect to the server - test each IP till one works */ for (h = &shost; h != NULL; h = h->next) { fd = (Ustrchr(h->address, ':') != 0) ? (fd6 < 0) ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6 : (fd4 < 0) ? (fd4 = ip_socket(type, af = AF_INET )) : fd4; if (fd < 0) { *errstr = string_sprintf("failed to create socket: %s", strerror(errno)); goto bad; } for(port = portlo; port <= porthi; port++) if (ip_connect(fd, af, h->address, port, timeout) == 0) { if (fd != fd6) close(fd6); if (fd != fd4) close(fd4); if (connhost) { h->port = port; *connhost = *h; connhost->next = NULL; } return fd; } } *errstr = string_sprintf("failed to connect to any address for %s: %s", hostname, strerror(errno)); bad: close(fd4); close(fd6); return -1; }
static int internal_lsearch_find(void *handle, uschar *filename, uschar *keystring, int length, uschar **result, uschar **errmsg, int type) { FILE *f = (FILE *)handle; BOOL last_was_eol = TRUE; BOOL this_is_eol = TRUE; int old_pool = store_pool; void *reset_point = NULL; uschar buffer[4096]; /* Wildcard searches may use up some store, because of expansions. We don't want them to fill up our search store. What we do is set the pool to the main pool and get a point to reset to later. Wildcard searches could also issue lookups, but internal_search_find will take care of that, and the cache will be safely stored in the search pool again. */ if(type == LSEARCH_WILD || type == LSEARCH_NWILD) { store_pool = POOL_MAIN; reset_point = store_get(0); } filename = filename; /* Keep picky compilers happy */ errmsg = errmsg; rewind(f); for (last_was_eol = TRUE; Ufgets(buffer, sizeof(buffer), f) != NULL; last_was_eol = this_is_eol) { int ptr, size; int p = Ustrlen(buffer); int linekeylength; BOOL this_is_comment; uschar *yield; uschar *s = buffer; /* Check whether this the final segment of a line. If it follows an incomplete part-line, skip it. */ this_is_eol = p > 0 && buffer[p-1] == '\n'; if (!last_was_eol) continue; /* We now have the start of a physical line. If this is a final line segment, remove trailing white space. */ if (this_is_eol) { while (p > 0 && isspace((uschar)buffer[p-1])) p--; buffer[p] = 0; } /* If the buffer is empty it might be (a) a complete empty line, or (b) the start of a line that begins with so much white space that it doesn't all fit in the buffer. In both cases we want to skip the entire physical line. If the buffer begins with # it is a comment line; if it begins with white space it is a logical continuation; again, we want to skip the entire physical line. */ if (buffer[0] == 0 || buffer[0] == '#' || isspace(buffer[0])) continue; /* We assume that they key will fit in the buffer. If the key starts with ", read it as a quoted string. We don't use string_dequote() because that uses new store for the result, and we may be doing this many times in a long file. We know that the dequoted string must be shorter than the original, because we are removing the quotes, and also any escape sequences always turn two or more characters into one character. Therefore, we can store the new string in the same buffer. */ if (*s == '\"') { uschar *t = s++; while (*s != 0 && *s != '\"') { if (*s == '\\') *t++ = string_interpret_escape(&s); else *t++ = *s; s++; } if (*s != 0) s++; /* Past terminating " */ linekeylength = t - buffer; } /* Otherwise it is terminated by a colon or white space */ else { while (*s != 0 && *s != ':' && !isspace(*s)) s++; linekeylength = s - buffer; } /* The matching test depends on which kind of lsearch we are doing */ switch(type) { /* A plain lsearch treats each key as a literal */ case LSEARCH_PLAIN: if (linekeylength != length || strncmpic(buffer, keystring, length) != 0) continue; break; /* Key matched */ /* A wild lsearch treats each key as a possible wildcarded string; no expansion is done for nwildlsearch. */ case LSEARCH_WILD: case LSEARCH_NWILD: { int rc; int save = buffer[linekeylength]; uschar *list = buffer; buffer[linekeylength] = 0; rc = match_isinlist(keystring, &list, UCHAR_MAX+1, /* Single-item list */ NULL, /* No anchor */ NULL, /* No caching */ MCL_STRING + ((type == LSEARCH_WILD)? 0:MCL_NOEXPAND), TRUE, /* Caseless */ NULL); buffer[linekeylength] = save; if (rc == FAIL) continue; if (rc == DEFER) return DEFER; } /* The key has matched. If the search involved a regular expression, it might have caused numerical variables to be set. However, their values will be in the wrong storage pool for external use. Copying them to the standard pool is not feasible because of the caching of lookup results - a repeated lookup will not match the regular expression again. Therefore, we flatten all numeric variables at this point. */ expand_nmax = -1; break; /* Compare an ip address against a list of network/ip addresses. We have to allow for the "*" case specially. */ case LSEARCH_IP: if (linekeylength == 1 && buffer[0] == '*') { if (length != 1 || keystring[0] != '*') continue; } else if (length == 1 && keystring[0] == '*') continue; else { int maskoffset; int save = buffer[linekeylength]; buffer[linekeylength] = 0; if (string_is_ip_address(buffer, &maskoffset) == 0 || !host_is_in_net(keystring, buffer, maskoffset)) continue; buffer[linekeylength] = save; } break; /* Key matched */ } /* The key has matched. Skip spaces after the key, and allow an optional colon after the spaces. This is an odd specification, but it's for compatibility. */ while (isspace((uschar)*s)) s++; if (*s == ':') { s++; while (isspace((uschar)*s)) s++; } /* Reset dynamic store, if we need to, and revert to the search pool */ if (reset_point != NULL) { store_reset(reset_point); store_pool = old_pool; } /* Now we want to build the result string to contain the data. There can be two kinds of continuation: (a) the physical line may not all have fitted into the buffer, and (b) there may be logical continuation lines, for which we must convert all leading white space into a single blank. Initialize, and copy the first segment of data. */ this_is_comment = FALSE; size = 100; ptr = 0; yield = store_get(size); if (*s != 0) yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); /* Now handle continuations */ for (last_was_eol = this_is_eol; Ufgets(buffer, sizeof(buffer), f) != NULL; last_was_eol = this_is_eol) { s = buffer; p = Ustrlen(buffer); this_is_eol = p > 0 && buffer[p-1] == '\n'; /* Remove trailing white space from a physical line end */ if (this_is_eol) { while (p > 0 && isspace((uschar)buffer[p-1])) p--; buffer[p] = 0; } /* If this is not a physical line continuation, skip it entirely if it's empty or starts with #. Otherwise, break the loop if it doesn't start with white space. Otherwise, replace leading white space with a single blank. */ if (last_was_eol) { this_is_comment = (this_is_comment || (buffer[0] == 0 || buffer[0] == '#')); if (this_is_comment) continue; if (!isspace((uschar)buffer[0])) break; while (isspace((uschar)*s)) s++; *(--s) = ' '; } if (this_is_comment) continue; /* Join a physical or logical line continuation onto the result string. */ yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); } yield[ptr] = 0; store_reset(yield + ptr + 1); *result = yield; return OK; } /* Reset dynamic store, if we need to */ if (reset_point != NULL) { store_reset(reset_point); store_pool = old_pool; } return FAIL; }
int ipliteral_router_entry( router_instance *rblock, /* data for this instantiation */ address_item *addr, /* address we are working on */ struct passwd *pw, /* passwd entry after check_local_user */ int verify, /* v_none/v_recipient/v_sender/v_expn */ address_item **addr_local, /* add it to this if it's local */ address_item **addr_remote, /* add it to this if it's remote */ address_item **addr_new, /* put new addresses on here */ address_item **addr_succeed) /* put old address here on success */ { /* ipliteral_router_options_block *ob = (ipliteral_router_options_block *)(rblock->options_block); */ host_item *h; const uschar *domain = addr->domain; const uschar *ip; int len = Ustrlen(domain); int rc, ipv; addr_new = addr_new; /* Keep picky compilers happy */ addr_succeed = addr_succeed; DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n", rblock->name, addr->address, addr->domain); /* Check that the domain is an IP address enclosed in square brackets. Remember to allow for the "official" form of IPv6 addresses. If not, the router declines. Otherwise route to the single IP address, setting the host name to "(unnamed)". */ if (domain[0] != '[' || domain[len-1] != ']') return DECLINE; ip = string_copyn(domain+1, len-2); if (strncmpic(ip, US"IPV6:", 5) == 0 || strncmpic(ip, US"IPV4:", 5) == 0) ip += 5; ipv = string_is_ip_address(ip, NULL); if (ipv == 0 || (disable_ipv6 && ipv == 6)) return DECLINE; /* It seems unlikely that ignore_target_hosts will be used with this router, but if it is set, it should probably work. */ if (verify_check_this_host(CUSS&rblock->ignore_target_hosts, NULL, domain, ip, NULL) == OK) { DEBUG(D_route) debug_printf("%s is in ignore_target_hosts\n", ip); addr->message = US"IP literal host explicitly ignored"; return DECLINE; } /* Set up a host item */ h = store_get(sizeof(host_item)); h->next = NULL; h->address = string_copy(ip); h->port = PORT_NONE; h->name = domain; h->mx = MX_NONE; h->status = hstatus_unknown; h->why = hwhy_unknown; h->last_try = 0; /* Determine whether the host is the local host, and if so, take action according to the configuration. */ if (host_scan_for_local_hosts(h, &h, NULL) == HOST_FOUND_LOCAL) { int rc = rf_self_action(addr, h, rblock->self_code, rblock->self_rewrite, rblock->self, addr_new); if (rc != OK) return rc; } /* Address is routed to this host */ addr->host_list = h; /* Set up the errors address, if any. */ rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address); if (rc != OK) return rc; /* Set up the additional and removable headers for this address. */ rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers, &addr->prop.remove_headers); if (rc != OK) return rc; /* Fill in the transport, queue the address for local or remote delivery, and yield success. For local delivery, of course, the IP address won't be used. If just verifying, there need not be a transport, in which case it doesn't matter which queue we put the address on. This is all now handled by the route_queue() function. */ if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr, rblock->name, NULL)) return DEFER; addr->transport = rblock->transport; return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER; }