static VSTRING *flush_site_to_path(VSTRING *path, const char *site) { const char *ptr; int ch; /* * Convert the name to ASCII, so that we don't to end up with non-ASCII * names in the file system. The IDNA library functions fold case. */ #ifndef NO_EAI if ((site = midna_domain_to_ascii(site)) == 0) return (0); #endif /* * Allocate buffer on the fly; caller still needs to clean up. */ if (path == 0) path = vstring_alloc(10); /* * Mask characters that could upset the name-to-queue-file mapping code. */ for (ptr = site; (ch = *(unsigned const char *) ptr) != 0; ptr++) if (ISALNUM(ch)) VSTRING_ADDCH(path, tolower(ch)); else VSTRING_ADDCH(path, '_'); VSTRING_TERMINATE(path); if (msg_verbose) msg_info("site %s to path %s", site, STR(path)); return (path); }
DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) { DNS_RR *addr_list; int res_opt = 0; const char *ahost; dsb_reset(why); /* Paranoia */ if (smtp_dns_support == SMTP_DNS_DNSSEC) res_opt |= RES_USE_DNSSEC; /* * IDNA support. */ #ifndef NO_EAI if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", host, ahost); } else #endif ahost = host; /* * If the host is specified by numerical address, just convert the * address to internal form. Otherwise, the host is specified by name. */ #define PREF0 0 addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why); if (addr_list && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) && smtp_find_self(addr_list) != 0) { dns_rr_free(addr_list); dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host); return (0); } if (addr_list && addr_list->next) { if (var_smtp_rand_addr) addr_list = dns_rr_shuffle(addr_list); /* The following changes the order of equal-preference hosts. */ if (inet_proto_info()->ai_family_list[1] != 0) addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); } if (msg_verbose) smtp_print_addr(host, addr_list); return (addr_list); }
int valid_utf8_hostname(int enable_utf8, const char *name, int gripe) { static const char myname[] = "valid_utf8_hostname"; /* * Trivial cases first. */ if (*name == 0) { if (gripe) msg_warn("%s: empty domain name", myname); return (0); } /* * Convert non-ASCII domain name to ASCII and validate the result per * STD3. midna_domain_to_ascii() applies valid_hostname() to the result. * Propagate the gripe parameter for better diagnostics (note that * midna_domain_to_ascii() logs a problem only when the result is not * cached). */ #ifndef NO_EAI if (enable_utf8 && !allascii(name)) { if (midna_domain_to_ascii(name) == 0) { if (gripe) msg_warn("%s: malformed UTF-8 domain name", myname); return (0); } else { return (1); } } #endif /* * Validate ASCII name per STD3. */ return (valid_hostname(name, gripe)); }
static int match_servername(const char *certid, const TLS_CLIENT_START_PROPS *props) { const ARGV *cmatch_argv; const char *nexthop = props->nexthop; const char *hname = props->host; const char *domain; const char *parent; const char *aname; int match_subdomain; int i; int idlen; int domlen; if ((cmatch_argv = props->matchargv) == 0) return 0; #ifndef NO_EAI /* * DNS subjectAltNames are required to be ASCII. * * Per RFC 6125 Section 6.4.4 Matching the CN-ID, follows the same rules * (6.4.1, 6.4.2 and 6.4.3) that apply to subjectAltNames. In * particular, 6.4.2 says that the reference identifier is coerced to * ASCII, but no conversion is stated or implied for the CN-ID, so it * seems it only matches if it is all ASCII. Otherwise, it is some other * sort of name. */ if (!allascii(certid)) return (0); if (!allascii(nexthop) && (aname = midna_domain_to_ascii(nexthop)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", nexthop, aname); nexthop = aname; } #endif /* * Match the certid against each pattern until we find a match. */ for (i = 0; i < cmatch_argv->argc; ++i) { match_subdomain = 0; if (!strcasecmp(cmatch_argv->argv[i], "nexthop")) domain = nexthop; else if (!strcasecmp(cmatch_argv->argv[i], "hostname")) domain = hname; else if (!strcasecmp(cmatch_argv->argv[i], "dot-nexthop")) { domain = nexthop; match_subdomain = 1; } else { domain = cmatch_argv->argv[i]; if (*domain == '.') { if (domain[1]) { ++domain; match_subdomain = 1; } } #ifndef NO_EAI /* * Besides U+002E (full stop) IDNA2003 allows labels to be * separated by any of the Unicode variants U+3002 (ideographic * full stop), U+FF0E (fullwidth full stop), and U+FF61 * (halfwidth ideographic full stop). Their respective UTF-8 * encodings are: E38082, EFBC8E and EFBDA1. * * IDNA2008 does not permit (upper) case and other variant * differences in U-labels. The midna_domain_to_ascii() function, * based on UTS46, normalizes such differences away. * * The IDNA to_ASCII conversion does not allow empty leading labels, * so we handle these explicitly here. */ else { unsigned char *cp = (unsigned char *) domain; if ((cp[0] == 0xe3 && cp[1] == 0x80 && cp[2] == 0x82) || (cp[0] == 0xef && cp[1] == 0xbc && cp[2] == 0x8e) || (cp[0] == 0xef && cp[1] == 0xbd && cp[2] == 0xa1)) { if (domain[3]) { domain = domain + 3; match_subdomain = 1; } } } if (!allascii(domain) && (aname = midna_domain_to_ascii(domain)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", domain, aname); domain = aname; } #endif } /* * Sub-domain match: certid is any sub-domain of hostname. */ if (match_subdomain) { if ((idlen = strlen(certid)) > (domlen = strlen(domain)) + 1 && certid[idlen - domlen - 1] == '.' && !strcasecmp(certid + (idlen - domlen), domain)) return (1); else continue; } /* * Exact match and initial "*" match. The initial "*" in a certid * matches one (if var_tls_multi_label is false) or more hostname * components under the condition that the certid contains multiple * hostname components. */ if (!strcasecmp(certid, domain) || (certid[0] == '*' && certid[1] == '.' && certid[2] != 0 && (parent = strchr(domain, '.')) != 0 && (idlen = strlen(certid + 1)) <= (domlen = strlen(parent)) && strcasecmp(var_tls_multi_wildcard == 0 ? parent : parent + domlen - idlen, certid + 1) == 0)) return (1); } return (0); }
DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags, DSN_BUF *why, int *found_myself) { DNS_RR *mx_names; DNS_RR *addr_list = 0; DNS_RR *self = 0; unsigned best_pref; unsigned best_found; int r = 0; /* Resolver flags */ const char *aname; dsb_reset(why); /* Paranoia */ /* * Preferences from DNS use 0..32767, fall-backs use 32768+. */ #define IMPOSSIBLE_PREFERENCE (~0) /* * Sanity check. */ if (smtp_dns_support == SMTP_DNS_DISABLED) msg_panic("smtp_domain_addr: DNS lookup is disabled"); if (smtp_dns_support == SMTP_DNS_DNSSEC) r |= RES_USE_DNSSEC; /* * IDNA support. */ #ifndef NO_EAI if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", name, aname); } else #endif aname = name; /* * Look up the mail exchanger hosts listed for this name. Sort the * results by preference. Look up the corresponding host addresses, and * truncate the list so that it contains only hosts that are more * preferred than myself. When no MX resource records exist, look up the * addresses listed for this name. * * According to RFC 974: "It is possible that the list of MXs in the * response to the query will be empty. This is a special case. If the * list is empty, mailers should treat it as if it contained one RR, an * MX RR with a preference value of 0, and a host name of REMOTE. (I.e., * REMOTE is its only MX). In addition, the mailer should do no further * processing on the list, but should attempt to deliver the message to * REMOTE." * * Normally it is OK if an MX host cannot be found in the DNS; we'll just * use a backup one, and silently ignore the better MX host. However, if * the best backup that we can find in the DNS is the local machine, then * we must remember that the local machine is not the primary MX host, or * else we will claim that mail loops back. * * XXX Optionally do A lookups even when the MX lookup didn't complete. * Unfortunately with some DNS servers this is not a transient problem. * * XXX Ideally we would perform A lookups only as far as needed. But as long * as we're looking up all the hosts, it would be better to look up the * least preferred host first, so that DNS lookup error messages make * more sense. * * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX * hosts, whereas multiple A records per hostname must be used in the * order as received. They make the bogus assumption that a hostname with * multiple A records corresponds to one machine with multiple network * interfaces. * * XXX 2821: Postfix recognizes the local machine by looking for its own IP * address in the list of mail exchangers. RFC 2821 says one has to look * at the mail exchanger hostname as well, making the bogus assumption * that an IP address is listed only under one hostname. However, looking * at hostnames provides a partial solution for MX hosts behind a NAT * gateway. */ switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) { default: dsb_status(why, "4.4.3"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(aname, misc_flags, why); break; case DNS_INVAL: dsb_status(why, "5.4.4"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(aname, misc_flags, why); break; case DNS_NULLMX: dsb_status(why, "5.1.0"); break; case DNS_POLICY: dsb_status(why, "4.7.0"); break; case DNS_FAIL: dsb_status(why, "5.4.3"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(aname, misc_flags, why); break; case DNS_OK: mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); addr_list = smtp_addr_list(mx_names, why); if (mxrr) *mxrr = dns_rr_copy(mx_names); /* copies one record! */ dns_rr_free(mx_names); if (addr_list == 0) { /* Text does not change. */ if (var_smtp_defer_mxaddr) { /* Don't clobber the null terminator. */ if (SMTP_HAS_HARD_DSN(why)) SMTP_SET_SOFT_DSN(why); /* XXX */ /* Require some error status. */ else if (!SMTP_HAS_SOFT_DSN(why)) msg_panic("smtp_domain_addr: bad status"); } msg_warn("no MX host for %s has a valid address record", name); break; } best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); if (msg_verbose) smtp_print_addr(name, addr_list); if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) && (self = smtp_find_self(addr_list)) != 0) { addr_list = smtp_truncate_self(addr_list, self->pref); if (addr_list == 0) { if (best_pref != best_found) { dsb_simple(why, "4.4.4", "unable to find primary relay for %s", name); } else { dsb_simple(why, "5.4.6", "mail for %s loops back to myself", name); } } } #define SMTP_COMPARE_ADDR(flags) \ (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ dns_rr_compare_pref_any) if (addr_list && addr_list->next && var_smtp_rand_addr) { addr_list = dns_rr_shuffle(addr_list); addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); } break; case DNS_NOTFOUND: addr_list = smtp_host_addr(aname, misc_flags, why); break; } /* * Clean up. */ *found_myself |= (self != 0); return (addr_list); }