uschar * string_localpart_utf8_to_alabel(const uschar * utf8, uschar ** err) { size_t ucs4_len; punycode_uint * p; size_t p_len; uschar * res; int rc; if (!string_is_utf8(utf8)) return string_copy(utf8); p = (punycode_uint *) stringprep_utf8_to_ucs4(CCS utf8, -1, &ucs4_len); p_len = ucs4_len*4; /* this multiplier is pure guesswork */ res = store_get(p_len+5); res[0] = 'x'; res[1] = 'n'; res[2] = res[3] = '-'; if ((rc = punycode_encode(ucs4_len, p, NULL, &p_len, CS res+4)) != PUNYCODE_SUCCESS) { DEBUG(D_expand) debug_printf("l_u2a: bad '%s'\n", punycode_strerror(rc)); free(p); if (err) *err = US punycode_strerror(rc); return NULL; } p_len += 4; free(p); res[p_len] = '\0'; return res; }
int filename_to_win32 (char *targ, const char *src) { GError *gerr = NULL; gsize bytes; int err = 0; *targ = '\0'; if (string_is_utf8((const unsigned char *) src)) { gchar *tr = g_locale_from_utf8(src, -1, NULL, &bytes, &gerr); if (gerr != NULL) { fprintf(stderr, "Couldn't handle '%s': %s\n", src, gerr->message); g_error_free(gerr); err = 1; } else { strncat(targ, tr, MAXLEN - 1); g_free(tr); } } else { strncat(targ, src, MAXLEN - 1); } if (!err) { slash_convert(targ, TO_BACKSLASH); } return err; }
uschar * string_domain_utf8_to_alabel(const uschar * utf8, uschar ** err) { uschar * s1, * s; int rc; #ifdef SUPPORT_I18N_2008 /* Avoid lowercasing plain-ascii domains */ if (!string_is_utf8(utf8)) return string_copy(utf8); /* Only lowercase is accepted by the library call. A pity since we lose any mixed-case annotation. This does not really matter for a domain. */ { uschar c; for (s1 = s = US utf8; (c = *s1); s1++) if (!(c & 0x80) && isupper(c)) { s = string_copy(utf8); for (s1 = s + (s1 - utf8); (c = *s1); s1++) if (!(c & 0x80) && isupper(c)) *s1 = tolower(c); break; } } if ((rc = idn2_lookup_u8((const uint8_t *) s, &s1, IDN2_NFC_INPUT)) != IDN2_OK) { if (err) *err = US idn2_strerror(rc); return NULL; } #else s = US stringprep_utf8_nfkc_normalize(CCS utf8, -1); if ( (rc = idna_to_ascii_8z(CCS s, CSS &s1, IDNA_ALLOW_UNASSIGNED)) != IDNA_SUCCESS) { free(s); if (err) *err = US idna_strerror(rc); return NULL; } free(s); #endif s = string_copy(s1); free(s1); return s; }
int dns_lookup(dns_answer *dnsa, const uschar *name, int type, const uschar **fully_qualified_name) { int i; const uschar *orig_name = name; BOOL secure_so_far = TRUE; /* Loop to follow CNAME chains so far, but no further... */ for (i = 0; i < 10; i++) { uschar data[256]; dns_record *rr, cname_rr, type_rr; dns_scan dnss; int datalen, rc; /* DNS lookup failures get passed straight back. */ if ((rc = dns_basic_lookup(dnsa, name, type)) != DNS_SUCCEED) return rc; /* We should have either records of the required type, or a CNAME record, or both. We need to know whether both exist for getting the fully qualified name, but avoid scanning more than necessary. Note that we must copy the contents of any rr blocks returned by dns_next_rr() as they use the same area in the dnsa block. */ cname_rr.data = type_rr.data = NULL; for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) { if (rr->type == type) { if (type_rr.data == NULL) type_rr = *rr; if (cname_rr.data != NULL) break; } else if (rr->type == T_CNAME) cname_rr = *rr; } /* For the first time round this loop, if a CNAME was found, take the fully qualified name from it; otherwise from the first data record, if present. */ if (i == 0 && fully_qualified_name != NULL) { uschar * rr_name = cname_rr.data ? cname_rr.name : type_rr.data ? type_rr.name : NULL; if ( rr_name && Ustrcmp(rr_name, *fully_qualified_name) != 0 && rr_name[0] != '*' #ifdef EXPERIMENTAL_INTERNATIONAL && ( !string_is_utf8(*fully_qualified_name) || Ustrcmp(rr_name, string_domain_utf8_to_alabel(*fully_qualified_name, NULL)) != 0 ) #endif ) *fully_qualified_name = string_copy_dnsdomain(rr_name); } /* If any data records of the correct type were found, we are done. */ if (type_rr.data != NULL) { if (!secure_so_far) /* mark insecure if any element of CNAME chain was */ dns_set_insecure(dnsa); return DNS_SUCCEED; } /* If there are no data records, we need to re-scan the DNS using the domain given in the CNAME record, which should exist (otherwise we should have had a failure from dns_lookup). However code against the possibility of its not existing. */ if (cname_rr.data == NULL) return DNS_FAIL; datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, sizeof(data)); if (datalen < 0) return DNS_FAIL; name = data; if (!dns_is_secure(dnsa)) secure_so_far = FALSE; DEBUG(D_dns) debug_printf("CNAME found: change to %s\n", name); } /* Loop back to do another lookup */ /*Control reaches here after 10 times round the CNAME loop. Something isn't right... */ log_write(0, LOG_MAIN, "CNAME loop for %s encountered", orig_name); return DNS_FAIL; }
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 void add_generated(router_instance *rblock, address_item **addr_new, address_item *addr, address_item *generated, address_item_propagated *addr_prop, ugid_block *ugidptr, struct passwd *pw) { redirect_router_options_block *ob = (redirect_router_options_block *)(rblock->options_block); while (generated != NULL) { address_item *parent; address_item *next = generated; uschar *errors_address = next->prop.errors_address; generated = next->next; next->parent = addr; orflag(next, addr, af_ignore_error); next->start_router = rblock->redirect_router; if (addr->child_count == SHRT_MAX) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d " "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address); addr->child_count++; next->next = *addr_new; *addr_new = next; /* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */ if (ob->one_time && !queue_2stage) { for (parent = addr; parent->parent != NULL; parent = parent->parent); next->onetime_parent = parent->address; } if (ob->hide_child_in_errmsg) setflag(next, af_hide_child); /* If check_ancestor is set, we want to know if any ancestor of this address is the address we are about to generate. The check must be done caselessly unless the ancestor was routed by a case-sensitive router. */ if (ob->check_ancestor) { for (parent = addr; parent != NULL; parent = parent->parent) { if (((parent->router != NULL && parent->router->caseful_local_part)? Ustrcmp(next->address, parent->address) : strcmpic(next->address, parent->address) ) == 0) { DEBUG(D_route) debug_printf("generated parent replaced by child\n"); next->address = string_copy(addr->address); break; } } } /* A user filter may, under some circumstances, set up an errors address. If so, we must take care to re-instate it when we copy in the propagated data so that it overrides any errors_to setting on the router. */ next->prop = *addr_prop; if (errors_address != NULL) next->prop.errors_address = errors_address; /* For pipes, files, and autoreplies, record this router as handling them, because they don't go through the routing process again. Then set up uid, gid, home and current directories for transporting. */ if (testflag(next, af_pfr)) { next->router = rblock; rf_set_ugid(next, ugidptr); /* Will contain pw values if not overridden */ /* When getting the home directory out of the password information, wrap it in \N...\N to avoid expansion later. In Cygwin, home directories can contain $ characters. */ if (rblock->home_directory != NULL) next->home_dir = rblock->home_directory; else if (rblock->check_local_user) next->home_dir = string_sprintf("\\N%s\\N", pw->pw_dir); else if (rblock->router_home_directory != NULL && testflag(addr, af_home_expanded)) { next->home_dir = deliver_home; setflag(next, af_home_expanded); } next->current_dir = rblock->current_directory; /* Permission options */ if (!ob->forbid_pipe) setflag(next, af_allow_pipe); if (!ob->forbid_file) setflag(next, af_allow_file); if (!ob->forbid_filter_reply) setflag(next, af_allow_reply); /* If the transport setting fails, the error gets picked up at the outer level from the setting of basic_errno in the address. */ if (next->address[0] == '|') { address_pipe = next->address; if (rf_get_transport(ob->pipe_transport_name, &(ob->pipe_transport), next, rblock->name, US"pipe_transport")) next->transport = ob->pipe_transport; address_pipe = NULL; } else if (next->address[0] == '>') { if (rf_get_transport(ob->reply_transport_name, &(ob->reply_transport), next, rblock->name, US"reply_transport")) next->transport = ob->reply_transport; } else /* must be file or directory */ { int len = Ustrlen(next->address); address_file = next->address; if (next->address[len-1] == '/') { if (rf_get_transport(ob->directory_transport_name, &(ob->directory_transport), next, rblock->name, US"directory_transport")) next->transport = ob->directory_transport; } else { if (rf_get_transport(ob->file_transport_name, &(ob->file_transport), next, rblock->name, US"file_transport")) next->transport = ob->file_transport; } address_file = NULL; } } #ifdef SUPPORT_I18N next->prop.utf8_msg = string_is_utf8(next->address) || (sender_address && string_is_utf8(sender_address)); #endif DEBUG(D_route) { debug_printf("%s router generated %s\n %serrors_to=%s transport=%s\n", rblock->name, next->address, testflag(next, af_pfr)? "pipe, file, or autoreply\n " : "", next->prop.errors_address, (next->transport == NULL)? US"NULL" : next->transport->name); if (testflag(next, af_uid_set)) debug_printf(" uid=%ld ", (long int)(next->uid)); else debug_printf(" uid=unset "); if (testflag(next, af_gid_set)) debug_printf("gid=%ld ", (long int)(next->gid)); else debug_printf("gid=unset "); #ifdef SUPPORT_I18N if (next->prop.utf8_msg) debug_printf("utf8 "); #endif debug_printf("home=%s\n", next->home_dir); } } }