diff options
Diffstat (limited to 'src/sm_resolve.c')
| -rw-r--r-- | src/sm_resolve.c | 1340 |
1 files changed, 1224 insertions, 116 deletions
diff --git a/src/sm_resolve.c b/src/sm_resolve.c index 8ec2cb68e6bfc..79e4168715a39 100644 --- a/src/sm_resolve.c +++ b/src/sm_resolve.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2004, 2010 Proofpoint, Inc. and its suppliers. + * Copyright (c) 2000-2004, 2010, 2015, 2020 Proofpoint, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set @@ -42,14 +42,17 @@ */ #include <sendmail.h> -#if DNSMAP +#if DNSMAP || DANE # if NAMED_BIND # if NETINET # include <netinet/in_systm.h> # include <netinet/ip.h> -# endif /* NETINET */ +# endif +# define _DEFINE_SMR_GLOBALS 1 # include "sm_resolve.h" +#include <arpa/inet.h> + SM_RCSID("$Id: sm_resolve.c,v 8.40 2013-11-22 20:51:56 ca Exp $") static struct stot @@ -60,10 +63,10 @@ static struct stot { # if NETINET { "A", T_A }, -# endif /* NETINET */ +# endif # if NETINET6 { "AAAA", T_AAAA }, -# endif /* NETINET6 */ +# endif { "NS", T_NS }, { "CNAME", T_CNAME }, { "PTR", T_PTR }, @@ -71,10 +74,24 @@ static struct stot { "TXT", T_TXT }, { "AFSDB", T_AFSDB }, { "SRV", T_SRV }, +# ifdef T_DS + { "DS", T_DS }, +# endif + { "RRSIG", T_RRSIG }, +# ifdef T_NSEC + { "NSEC", T_NSEC }, +# endif +# ifdef T_DNSKEY + { "DNSKEY", T_DNSKEY }, +# endif + { "TLSA", T_TLSA }, { NULL, 0 } }; -static DNS_REPLY_T *parse_dns_reply __P((unsigned char *, int)); +static DNS_REPLY_T *parse_dns_reply __P((unsigned char *, int, unsigned int)); +# if DNSSEC_TEST && defined(T_TLSA) +static char *hex2bin __P((const char *, int)); +# endif /* ** DNS_STRING_TO_TYPE -- convert resource record name into type @@ -126,21 +143,23 @@ dns_type_to_string(type) ** DNS_FREE_DATA -- free all components of a DNS_REPLY_T ** ** Parameters: -** r -- pointer to DNS_REPLY_T +** dr -- pointer to DNS_REPLY_T ** ** Returns: ** none. */ void -dns_free_data(r) - DNS_REPLY_T *r; +dns_free_data(dr) + DNS_REPLY_T *dr; { RESOURCE_RECORD_T *rr; - if (r->dns_r_q.dns_q_domain != NULL) - sm_free(r->dns_r_q.dns_q_domain); - for (rr = r->dns_r_head; rr != NULL; ) + if (dr == NULL) + return; + if (dr->dns_r_q.dns_q_domain != NULL) + sm_free(dr->dns_r_q.dns_q_domain); + for (rr = dr->dns_r_head; rr != NULL; ) { RESOURCE_RECORD_T *tmp = rr; @@ -151,7 +170,59 @@ dns_free_data(r) rr = rr->rr_next; sm_free(tmp); } - sm_free(r); + sm_free(dr); +} + +/* +** BIN2HEX -- convert binary TLSA RR to hex string +** +** Parameters: +** tlsa -- pointer to result (allocated here) +** p -- binary data (TLSA RR) +** size -- length of p +** min_size -- minimum expected size +** +** Returns: +** >0: length of string (*tlsa) +** -1: error +*/ + +static int bin2hex __P((char **, unsigned char *, int, int)); + +static int +bin2hex(tlsa, p, size, min_size) + char **tlsa; + unsigned char *p; + int size; + int min_size; +{ + int i, pos, txtlen; + + txtlen = size * 3; + if (txtlen <= size || size < min_size) + { + if (LogLevel > 5) + sm_syslog(LOG_WARNING, NOQID, + "ERROR: bin2hex: size %d wrong", size); + return -1; + } + *tlsa = (char *) sm_malloc(txtlen); + if (*tlsa == NULL) + { + if (tTd(8, 17)) + sm_dprintf("len=%d, rr_data=NULL\n", txtlen); + return -1; + } + snprintf(*tlsa, txtlen, + "%02X %02X %02X", p[0], p[1], p[2]); + pos = strlen(*tlsa); + + /* why isn't there a print function like strlcat? */ + for (i = 3; i < size && pos < txtlen; i++, pos += 3) + snprintf(*tlsa + pos, txtlen - pos, "%c%02X", + (i == 3) ? ' ' : ':', p[i]); + + return i; } /* @@ -160,54 +231,72 @@ dns_free_data(r) ** Parameters: ** data -- pointer to dns data ** len -- len of data +** flags -- flags (RR_*) ** ** Returns: ** pointer to DNS_REPLY_T if succeeded. ** NULL otherwise. +** +** Note: +** use dns_free_data() to free() the result when no longer needed. */ static DNS_REPLY_T * -parse_dns_reply(data, len) +parse_dns_reply(data, len, flags) unsigned char *data; int len; + unsigned int flags; { unsigned char *p; unsigned short ans_cnt, ui; int status; size_t l; char host[MAXHOSTNAMELEN]; - DNS_REPLY_T *r; + DNS_REPLY_T *dr; RESOURCE_RECORD_T **rr; - r = (DNS_REPLY_T *) sm_malloc(sizeof(*r)); - if (r == NULL) + if (tTd(8, 90)) + { + FILE *fp; + + fp = fopen("dns.buffer", "w"); + if (fp != NULL) + { + fwrite(data, 1, len, fp); + fclose(fp); + fp = NULL; + } + else + sm_dprintf("parse_dns_reply: fp=%p, e=%d\n", + (void *)fp, errno); + } + + dr = (DNS_REPLY_T *) sm_malloc(sizeof(*dr)); + if (dr == NULL) return NULL; - memset(r, 0, sizeof(*r)); + memset(dr, 0, sizeof(*dr)); p = data; /* doesn't work on Crays? */ - memcpy(&r->dns_r_h, p, sizeof(r->dns_r_h)); - p += sizeof(r->dns_r_h); + memcpy(&dr->dns_r_h, p, sizeof(dr->dns_r_h)); + p += sizeof(dr->dns_r_h); status = dn_expand(data, data + len, p, host, sizeof(host)); if (status < 0) - { - dns_free_data(r); - return NULL; - } - r->dns_r_q.dns_q_domain = sm_strdup(host); - if (r->dns_r_q.dns_q_domain == NULL) - { - dns_free_data(r); - return NULL; - } + goto error; + dr->dns_r_q.dns_q_domain = sm_strdup(host); + if (dr->dns_r_q.dns_q_domain == NULL) + goto error; - ans_cnt = ntohs((unsigned short) r->dns_r_h.ancount); + ans_cnt = ntohs((unsigned short) dr->dns_r_h.ancount); + if (tTd(8, 17)) + sm_dprintf("parse_dns_reply: ac=%d, ad=%d\n", ans_cnt, + dr->dns_r_h.ad); p += status; - GETSHORT(r->dns_r_q.dns_q_type, p); - GETSHORT(r->dns_r_q.dns_q_class, p); - rr = &r->dns_r_head; + GETSHORT(dr->dns_r_q.dns_q_type, p); + GETSHORT(dr->dns_r_q.dns_q_class, p); + rr = &dr->dns_r_head; ui = 0; while (p < data + len && ui < ans_cnt) { @@ -215,10 +304,7 @@ parse_dns_reply(data, len) status = dn_expand(data, data + len, p, host, sizeof(host)); if (status < 0) - { - dns_free_data(r); - return NULL; - } + goto error; ++ui; p += status; GETSHORT(type, p); @@ -236,22 +322,15 @@ parse_dns_reply(data, len) sm_syslog(LOG_WARNING, NOQID, "ERROR: DNS RDLENGTH=%d > data len=%d", size, len - (int)(p - data)); - dns_free_data(r); - return NULL; + goto error; } *rr = (RESOURCE_RECORD_T *) sm_malloc(sizeof(**rr)); if (*rr == NULL) - { - dns_free_data(r); - return NULL; - } + goto error; memset(*rr, 0, sizeof(**rr)); (*rr)->rr_domain = sm_strdup(host); if ((*rr)->rr_domain == NULL) - { - dns_free_data(r); - return NULL; - } + goto error; (*rr)->rr_type = type; (*rr)->rr_class = class; (*rr)->rr_ttl = ttl; @@ -264,16 +343,13 @@ parse_dns_reply(data, len) status = dn_expand(data, data + len, p, host, sizeof(host)); if (status < 0) - { - dns_free_data(r); - return NULL; - } + goto error; + if (tTd(8, 50)) + sm_dprintf("parse_dns_reply: type=%s, host=%s\n", + dns_type_to_string(type), host); (*rr)->rr_u.rr_txt = sm_strdup(host); if ((*rr)->rr_u.rr_txt == NULL) - { - dns_free_data(r); - return NULL; - } + goto error; break; case T_MX: @@ -281,39 +357,30 @@ parse_dns_reply(data, len) status = dn_expand(data, data + len, p + 2, host, sizeof(host)); if (status < 0) - { - dns_free_data(r); - return NULL; - } + goto error; l = strlen(host) + 1; (*rr)->rr_u.rr_mx = (MX_RECORD_T *) sm_malloc(sizeof(*((*rr)->rr_u.rr_mx)) + l); if ((*rr)->rr_u.rr_mx == NULL) - { - dns_free_data(r); - return NULL; - } + goto error; (*rr)->rr_u.rr_mx->mx_r_preference = (p[0] << 8) | p[1]; (void) sm_strlcpy((*rr)->rr_u.rr_mx->mx_r_domain, host, l); + if (tTd(8, 50)) + sm_dprintf("mx=%s, pref=%d\n", host, + (*rr)->rr_u.rr_mx->mx_r_preference); break; case T_SRV: status = dn_expand(data, data + len, p + 6, host, sizeof(host)); if (status < 0) - { - dns_free_data(r); - return NULL; - } + goto error; l = strlen(host) + 1; (*rr)->rr_u.rr_srv = (SRV_RECORDT_T*) sm_malloc(sizeof(*((*rr)->rr_u.rr_srv)) + l); if ((*rr)->rr_u.rr_srv == NULL) - { - dns_free_data(r); - return NULL; - } + goto error; (*rr)->rr_u.rr_srv->srv_r_priority = (p[0] << 8) | p[1]; (*rr)->rr_u.rr_srv->srv_r_weight = (p[2] << 8) | p[3]; (*rr)->rr_u.rr_srv->srv_r_port = (p[4] << 8) | p[5]; @@ -340,38 +407,850 @@ parse_dns_reply(data, len) sm_syslog(LOG_WARNING, NOQID, "ERROR: DNS TXT record size=%d <= text len=%d", size, txtlen); - dns_free_data(r); - return NULL; + goto error; } (*rr)->rr_u.rr_txt = (char *) sm_malloc(txtlen + 1); if ((*rr)->rr_u.rr_txt == NULL) - { - dns_free_data(r); - return NULL; - } + goto error; (void) sm_strlcpy((*rr)->rr_u.rr_txt, (char*) p + 1, txtlen + 1); break; +# ifdef T_TLSA + case T_TLSA: + if (tTd(8, 61)) + sm_dprintf("parse_dns_reply: TLSA, size=%d, flags=%X\n", + size, flags); + if ((flags & RR_AS_TEXT) != 0) + { + txtlen = bin2hex((char **)&((*rr)->rr_u.rr_data), + p, size, 4); + if (txtlen <= 0) + goto error; + break; + } + /* FALLTHROUGH */ + /* return "raw" data for caller to use as it pleases */ +# endif /* T_TLSA */ + default: (*rr)->rr_u.rr_data = (unsigned char*) sm_malloc(size); if ((*rr)->rr_u.rr_data == NULL) + goto error; + (void) memcpy((*rr)->rr_u.rr_data, p, size); + if (tTd(8, 61) && type == T_A) { - dns_free_data(r); - return NULL; + SOCKADDR addr; + + (void) memcpy((void *)&addr.sin.sin_addr.s_addr, p, size); + sm_dprintf("parse_dns_reply: IPv4=%s\n", + inet_ntoa(addr.sin.sin_addr)); } - (void) memcpy((*rr)->rr_u.rr_data, p, size); break; } p += size; rr = &(*rr)->rr_next; } *rr = NULL; - return r; + return dr; + + error: + dns_free_data(dr); + return NULL; +} + +# if DNSSEC_TEST + +#include <arpa/nameser.h> + +static int gen_dns_reply __P((unsigned char *, int, unsigned char *, + const char *, int, const char *, int, int, int, int, + const char *, int, int, int)); +static int dnscrtrr __P((const char *, const char *, int, char *, int, + unsigned int, int *, int *, unsigned char *, int, unsigned char *)); + +/* +** HERRNO2TXT -- return error text for h_errno +** +** Parameters: +** e -- h_errno +** +** Returns: +** DNS error text if available +*/ + +const char * +herrno2txt(e) + int e; +{ + switch (e) + { + case NETDB_INTERNAL: + return "see errno"; + case NETDB_SUCCESS: + return "OK"; + case HOST_NOT_FOUND: + return "HOST_NOT_FOUND"; + case TRY_AGAIN: + return "TRY_AGAIN"; + case NO_RECOVERY: + return "NO_RECOVERY"; + case NO_DATA: + return "NO_DATA"; + } + return "bogus h_errno"; } /* -** DNS_LOOKUP_INT -- perform dns map lookup (internal helper routine) +** GEN_DNS_REPLY -- generate DNS reply data. +** +** Parameters: +** buf -- buffer to which DNS data is written +** buflen -- length of buffer +** bufpos -- position in buffer where DNS RRs are appended +** query -- name of query +** qtype -- resource record type of query +** domain -- name of domain which has been "found" +** class -- resource record class +** type -- resource record type +** ttl -- TTL +** size -- size of data +** data -- data +** txtlen -- length of text +** pref -- MX preference +** ad -- ad flag +** +** Returns: +** >0 length of buffer that has been used. +** <0 error +*/ + +static int +gen_dns_reply(buf, buflen, bufpos, query, qtype, domain, class, type, ttl, size, data, txtlen, pref, ad) + unsigned char *buf; + int buflen; + unsigned char *bufpos; + const char *query; + int qtype; + const char *domain; + int class; + int type; + int ttl; + int size; + const char *data; + int txtlen; + int pref; + int ad; +{ + unsigned short ans_cnt; + HEADER *hp; + unsigned char *cp, *ep; + int n; + static unsigned char *dnptrs[20], **dpp, **lastdnptr; + + SM_REQUIRE(NULL != buf); + SM_REQUIRE(buflen >= HFIXEDSZ); + SM_REQUIRE(query != NULL); + hp = (HEADER *) buf; + ep = buf + buflen; + cp = buf + HFIXEDSZ; + + if (bufpos != NULL) + cp = bufpos; + else + { + sm_dprintf("gen_dns_reply: query=%s, domain=%s, type=%s, size=%d, ad=%d\n", + query, domain, dns_type_to_string(type), size, ad); + dpp = dnptrs; + *dpp++ = buf; + *dpp++ = NULL; + lastdnptr = dnptrs + sizeof dnptrs / sizeof dnptrs[0]; + + memset(buf, 0, HFIXEDSZ); + hp->id = 0xdead; /* HACK */ + hp->qr = 1; + hp->opcode = QUERY; + hp->rd = 0; /* recursion desired? */ + hp->rcode = 0; /* !!! */ + /* hp->aa = ?; * !!! */ + /* hp->tc = ?; * !!! */ + /* hp->ra = ?; * !!! */ + hp->qdcount = htons(1); + hp->ancount = 0; + + n = dn_comp(query, cp, ep - cp - QFIXEDSZ, dnptrs, lastdnptr); + if (n < 0) + return n; + cp += n; + PUTSHORT(qtype, cp); + PUTSHORT(class, cp); + } + hp->ad = ad; + + if (ep - cp < QFIXEDSZ) + return (-1); + n = dn_comp(domain, cp, ep - cp - QFIXEDSZ, dnptrs, lastdnptr); + if (n < 0) + return n; + cp += n; + PUTSHORT(type, cp); + PUTSHORT(class, cp); + PUTLONG(ttl, cp); + + ans_cnt = ntohs((unsigned short) hp->ancount); + ++ans_cnt; + hp->ancount = htons((unsigned short) ans_cnt); + + switch (type) + { + case T_MX: + n = dn_comp(data, cp + 4, ep - cp - QFIXEDSZ, dnptrs, lastdnptr); + if (n < 0) + return n; + PUTSHORT(n + 2, cp); + PUTSHORT(pref, cp); + cp += n; + break; + + case T_TXT: + if (txtlen >= size) + return -1; + PUTSHORT(txtlen, cp); + (void) sm_strlcpy((char *)cp, data, txtlen + 1); + cp += txtlen; + break; + + case T_CNAME: + n = dn_comp(data, cp + 2, ep - cp - QFIXEDSZ, dnptrs, lastdnptr); + if (n < 0) + return n; + PUTSHORT(n, cp); + cp += n; + break; + +# if defined(T_TLSA) + case T_TLSA: + { + char *tlsa; + + tlsa = hex2bin(data, size); + if (tlsa == NULL) + return (-1); + n = size / 2; + PUTSHORT(n, cp); + (void) memcpy(cp, tlsa, n); + cp += n; + } + break; +# endif /* T_TLSA */ + + default: + PUTSHORT(size, cp); + (void) memcpy(cp, data, size); + cp += size; + break; + } + + return (cp - buf); +} + +/* +** SETHERRNOFROMSTRING -- set h_errno based on text +** +** Parameters: +** str -- string which might contain h_errno text +** prc -- pointer to rcode (EX_*) +** +** Returns: +** h_errno if found +** 0 otherwise +*/ + +int +setherrnofromstring(str, prc) + const char *str; + int *prc; +{ + SM_SET_H_ERRNO(0); + if (str == NULL || *str == '\0') + return 0; + if (strstr(str, "herrno:") == NULL) + return 0; + if (prc != NULL) + *prc = EX_NOHOST; + if (strstr(str, "host_not_found")) + SM_SET_H_ERRNO(HOST_NOT_FOUND); + else if (strstr(str, "try_again")) + { + SM_SET_H_ERRNO(TRY_AGAIN); + if (prc != NULL) + *prc = EX_TEMPFAIL; + } + else if (strstr(str, "no_recovery")) + SM_SET_H_ERRNO(NO_RECOVERY); + else if (strstr(str, "no_data")) + SM_SET_H_ERRNO(NO_DATA); + else + SM_SET_H_ERRNO(NETDB_INTERNAL); + return h_errno; +} + +/* +** GETTTLFROMSTRING -- extract ttl from a string +** +** Parameters: +** str -- string which might contain ttl +** +** Returns: +** ttl if found +** 0 otherwise +*/ + +int +getttlfromstring(str) + const char *str; +{ + if (str == NULL || *str == '\0') + return 0; +#define TTL_PRE "ttl=" + if (strstr(str, TTL_PRE) == NULL) + return 0; + return strtoul(str + strlen(TTL_PRE), NULL, 10); +} + +/* +** NSPORTIP -- parse port@IPv4 and set NS accordingly +** +** Parameters: +** p -- port@Ipv4 +** +** Returns: +** <0: error +** >0: ok +** +** Side Effects: +** sets NS for DNS lookups +*/ + +/* +** There should be a generic function for this... +** milter_open(), socket_map_open(), others? +*/ + +int +nsportip(p) + char *p; +{ + char *h; + int r; + unsigned short port; + struct in_addr nsip; + + if (p == NULL || *p == '\0') + return -1; + + port = 0; + while (SM_ISSPACE(*p)) + p++; + if (*p == '\0') + return -1; + h = strchr(p, '@'); + if (h != NULL) + { + *h = '\0'; + if (isascii(*p) && isdigit(*p)) + port = atoi(p); + *h = '@'; + p = h + 1; + } + h = strchr(p, ' '); + if (h != NULL) + *h = '\0'; + r = inet_pton(AF_INET, p, &nsip); + if (r > 0) + { + if ((_res.options & RES_INIT) == 0) + (void) res_init(); + dns_setns(&nsip, port); + } + if (h != NULL) + *h = ' '; + return r > 0 ? 0 : -1; +} + +/* +** DNS_SETNS -- set one NS in resolver context +** +** Parameters: +** ns -- (IPv4 address of) nameserver +** port -- nameserver port +** +** Returns: +** None. +*/ + +void +dns_setns(ns, port) + struct in_addr *ns; + unsigned int port; +{ + _res.nsaddr_list[0].sin_family = AF_INET; + _res.nsaddr_list[0].sin_addr = *ns; + if (port != 0) + _res.nsaddr_list[0].sin_port = htons(port); + _res.nscount = 1; + if (tTd(8, 61)) + sm_dprintf("dns_setns(%s,%u)\n", inet_ntoa(*ns), port); +} + +# if defined(T_TLSA) +/* +** HEX2BIN -- convert hex string to binary TLSA RR +** +** Parameters: +** p -- hex representation of TLSA RR +** size -- length of p +** +** Returns: +** pointer to binary TLSA RR +** NULL: error +*/ + +static char * +hex2bin(p, size) + const char *p; + int size; +{ + int i, pos, txtlen; + char *tlsa; + + txtlen = size / 2; + if (txtlen * 2 == size) + { + if (LogLevel > 5) + sm_syslog(LOG_WARNING, NOQID, + "ERROR: hex2bin: size %d wrong", size); + return NULL; + } + tlsa = sm_malloc(txtlen + 1); + if (tlsa == NULL) + { + if (tTd(8, 17)) + sm_dprintf("len=%d, tlsa=NULL\n", txtlen); + return NULL; + } + +#define CHAR2INT(c) (((c) <= '9') ? ((c) - '0') : (toupper(c) - 'A' + 10)) + for (i = 0, pos = 0; i + 1 < size && pos < txtlen; i += 2, pos++) + tlsa[pos] = CHAR2INT(p[i]) * 16 + CHAR2INT(p[i+1]); + + return tlsa; +} +# endif /* T_TLSA */ + +const char * +rr_type2tag(rr_type) + int rr_type; +{ + switch (rr_type) + { + case T_A: + return "ipv4"; +# if NETINET6 + case T_AAAA: + return "ipv6"; +# endif + case T_CNAME: + return "cname"; + case T_MX: + return "mx"; +# ifdef T_TLSA + case T_TLSA: + return "tlsa"; +# endif + } + return NULL; +} + +/* +** DNSCRTRR -- create DNS RR +** +** Parameters: +** domain -- original query domain +** query -- name of query +** qtype -- resource record type of query +** value -- (list of) data to set +** rr_type -- resource record type +** flags -- flags how to handle various lookups +** herr -- (pointer to) h_errno (output if non-NULL) +** adp -- (pointer to) ad flag +** answer -- buffer for RRs +** anslen -- size of answer +** anspos -- current position in answer +** +** Returns: +** >0: length of data in answer +** <0: error, check *herr +*/ + +static int +dnscrtrr(domain, query, qtype, value, rr_type, flags, herr, adp, answer, anslen, anspos) + const char *domain; + const char *query; + int qtype; + char *value; + int rr_type; + unsigned int flags; + int *herr; + int *adp; + unsigned char *answer; + int anslen; + unsigned char *anspos; +{ + SOCKADDR addr; + int ttl, ad, rlen; + char *p, *token; + char data[IN6ADDRSZ]; + char rhs[MAXLINE]; + + rlen = -1; + if (NULL == value || '\0' == *value) + return rlen; + SM_REQUIRE(adp != NULL); + (void) sm_strlcpy(rhs, value, sizeof(rhs)); + p = rhs; + if (setherrnofromstring(p, NULL) != 0) + { + if (herr != NULL) + *herr = h_errno; + if (tTd(8, 16)) + sm_dprintf("dnscrtrr rhs=%s h_errno=%d (%s)\n", + p, h_errno, herrno2txt(h_errno)); + return rlen; + } + + ttl = 0; + ad = 0; + for (token = p; token != NULL && *token != '\0'; token = p) + { + rlen = 0; + while (p != NULL && *p != '\0' && !SM_ISSPACE(*p)) + ++p; + if (SM_ISSPACE(*p)) + *p++ = '\0'; + sm_dprintf("dnscrtrr: token=%s\n", token); + if (strcmp(token, "ad") == 0) + { + bool adflag; + + adflag = (_res.options & RES_USE_DNSSEC) != 0; + + /* maybe print this only for the final RR? */ + if (tTd(8, 61)) + sm_dprintf("dnscrtrr: ad=1, adp=%d, adflag=%d\n", + *adp, adflag); + if (*adp != 0 && adflag) + { + *adp = 1; + ad = 1; + } + continue; + } + if (ttl == 0 && (ttl = getttlfromstring(token)) > 0) + { + if (tTd(8, 61)) + sm_dprintf("dnscrtrr: ttl=%d\n", ttl); + continue; + } + + if (rr_type == T_A) + { + addr.sin.sin_addr.s_addr = inet_addr(token); + (void) memmove(data, (void *)&addr.sin.sin_addr.s_addr, + INADDRSZ); + rlen = gen_dns_reply(answer, anslen, anspos, + query, qtype, domain, C_IN, rr_type, ttl, + INADDRSZ, data, 0, 0, ad); + } + +# if NETINET6 + if (rr_type == T_AAAA) + { + anynet_pton(AF_INET6, token, &addr.sin6.sin6_addr); + memmove(data, (void *)&addr.sin6.sin6_addr, IN6ADDRSZ); + rlen = gen_dns_reply(answer, anslen, anspos, + query, qtype, domain, C_IN, rr_type, ttl, + IN6ADDRSZ, data, 0, 0, ad); + } +# endif /* NETINET6 */ + + if (rr_type == T_MX) + { + char *endptr; + int pref; + + pref = (int) strtoul(token, &endptr, 10); + if (endptr == NULL || *endptr != ':') + goto error; + token = endptr + 1; + rlen = gen_dns_reply(answer, anslen, anspos, + query, qtype, domain, C_IN, rr_type, ttl, + strlen(token) + 1, token, 0, pref, ad); + if (tTd(8, 50)) + sm_dprintf("dnscrtrr: mx=%s, pref=%d\n", + token, pref); + } + +# ifdef T_TLSA + if (rr_type == T_TLSA) + rlen = gen_dns_reply(answer, anslen, anspos, + query, qtype, domain, C_IN, rr_type, ttl, + strlen(token) + 1, token, 0, 0, ad); +# endif + + if (rr_type == T_CNAME) + rlen = gen_dns_reply(answer, anslen, anspos, + query, qtype, domain, C_IN, rr_type, ttl, + strlen(token), token, 0, 0, ad); + if (rlen < 0) + goto error; + if (rlen > 0) + anspos = answer + rlen; + } + + if (ad != 1) + *adp = 0; + + return rlen; + + error: + if (herr != NULL && 0 == *herr) + *herr = NO_RECOVERY; + return -1; +} + +/* +** TSTDNS_SEARCH -- replacement for res_search() for testing +** +** Parameters: +** domain -- query domain +** class -- class +** type -- resource record type +** answer -- buffer for RRs +** anslen -- size of answer +** +** Returns: +** >0: length of data in answer +** <0: error, check h_errno +*/ + +int +tstdns_search(domain, class, type, answer, anslen) + const char *domain; + int class; + int type; + unsigned char *answer; + int anslen; +{ + int rlen, ad, maprcode, cnt, flags, herr; + bool found_cname; + const char *query; + char *p; + const char *tag; + char *av[2]; + STAB *map; + char key[MAXNAME + 16]; + char rhs[MAXLINE]; + unsigned char *anspos; + + rlen = -1; + herr = 0; + if (class != C_IN) + goto error; + if (NULL == domain || '\0' == *domain) + goto error; + tag = rr_type2tag(type); + if (tag == NULL) + goto error; + maprcode = EX_OK; + ad = -1; + flags = 0; + query = domain; + anspos = NULL; + + map = stab("access", ST_MAP, ST_FIND); + if (NULL == map) + { + sm_dprintf("access map not found\n"); + goto error; + } + if (!bitset(MF_OPEN, map->s_map.map_mflags) && + !openmap(&(map->s_map))) + { + sm_dprintf("access map open failed\n"); + goto error; + } + +/* +** Look up tag:domain, if not found and domain does not end with a dot +** (and the proper debug level is selected), also try with trailing dot. +*/ + +#define SM_LOOKUP2(tag) \ + do { \ + int len; \ + \ + len = strlen(domain); \ + av[0] = key; \ + av[1] = NULL; \ + snprintf(key, sizeof(key), "%s:%s", tag, domain); \ + p = (*map->s_map.map_class->map_lookup)(&map->s_map, key, av, \ + &maprcode); \ + if (p != NULL) \ + break; \ + if (!tTd(8, 112) || (len > 0 && '.' == domain[len - 1])) \ + break; \ + snprintf(key, sizeof(key), "%s:%s.", tag, domain); \ + p = (*map->s_map.map_class->map_lookup)(&map->s_map, key, av, \ + &maprcode); \ + } while (0) + + cnt = 0; + found_cname = false; + while (cnt < 6) + { + char *last; + + /* Should this try with/without trailing dot? */ + SM_LOOKUP2(tag); + if (p != NULL) + { + sm_dprintf("access map lookup key=%s, value=%s\n", key, + p); + break; + } + if (NULL == p && (flags & RR_NO_CNAME) == 0) + { + sm_dprintf("access map lookup failed key=%s, try cname\n", + key); + SM_LOOKUP2("cname"); + if (p != NULL) + { + sm_dprintf("cname lookup key=%s, value=%s, ad=%d\n", + key, p, ad); + rlen = dnscrtrr(domain, query, type, p, T_CNAME, + flags, &herr, &ad, answer, + anslen, anspos); + if (rlen < 0) + goto error; + if (rlen > 0) + anspos = answer + rlen; + found_cname = true; + } + } + if (NULL == p) + break; + + (void) sm_strlcpy(rhs, p, sizeof(rhs)); + p = rhs; + + /* skip (leading) ad/ttl: look for last ' ' */ + if ((last = strrchr(p, ' ')) != NULL && last[1] != '\0') + domain = last + 1; + else + domain = p; + ++cnt; + } + if (NULL == p) + { + int t; + char *tags[] = { "ipv4", "mx", "tlsa", +# if NETINET6 + "ipv6", +# endif + NULL + }; + + for (t = 0; tags[t] != NULL; t++) + { + if (strcmp(tag, tags[t]) == 0) + continue; + SM_LOOKUP2(tags[t]); + if (p != NULL) + { + sm_dprintf("access map lookup failed key=%s:%s, but found key=%s\n", + tag, domain, key); + herr = NO_DATA; + goto error; + } + } + sm_dprintf("access map lookup failed key=%s\n", key); + herr = HOST_NOT_FOUND; + goto error; + } + if (found_cname && (flags & RR_ONLY_CNAME) != 0) + return rlen; + rlen = dnscrtrr(domain, query, type, p, type, flags, &herr, &ad, + answer, anslen, anspos); + if (rlen < 0) + goto error; + return rlen; + + error: + if (0 == herr) + herr = NO_RECOVERY; + SM_SET_H_ERRNO(herr); + sm_dprintf("rlen=%d, herr=%d\n", rlen, herr); + return -1; +} + +/* +** TSTDNS_QUERYDOMAIN -- replacement for res_querydomain() for testing +** +** Parameters: +** name -- query name +** domain -- query domain +** class -- class +** type -- resource record type +** answer -- buffer for RRs +** anslen -- size of answer +** +** Returns: +** >0: length of data in answer +** <0: error, check h_errno +*/ + +int +tstdns_querydomain(name, domain, class, type, answer, anslen) + const char *name; + const char *domain; + int class; + int type; + unsigned char *answer; + int anslen; +{ + char query[MAXNAME]; + int len; + + if (NULL == name) + goto error; + if (NULL == domain || '\0' == *domain) + return tstdns_search(name, class, type, answer, anslen); + + len = snprintf(query, sizeof(query), "%s.%s", name, domain); + if (len >= (int)sizeof(query)) + goto error; + return tstdns_search(query, class, type, answer, anslen); + + error: + SM_SET_H_ERRNO(NO_RECOVERY); + return -1; +} + +# endif /* DNSSEC_TEST */ + +/* +** DNS_LOOKUP_INT -- perform DNS lookup ** ** Parameters: ** domain -- name to lookup @@ -379,6 +1258,10 @@ parse_dns_reply(data, len) ** rr_type -- resource record type ** retrans -- retransmission timeout ** retry -- number of retries +** options -- DNS resolver options +** flags -- currently only passed to parse_dns_reply() +** err -- (pointer to) errno (output if non-NULL) +** herr -- (pointer to) h_errno (output if non-NULL) ** ** Returns: ** result of lookup if succeeded. @@ -386,33 +1269,55 @@ parse_dns_reply(data, len) */ DNS_REPLY_T * -dns_lookup_int(domain, rr_class, rr_type, retrans, retry) +dns_lookup_int(domain, rr_class, rr_type, retrans, retry, options, flags, err, herr) const char *domain; int rr_class; int rr_type; time_t retrans; int retry; + unsigned int options; + unsigned int flags; + int *err; + int *herr; { int len; unsigned long old_options = 0; time_t save_retrans = 0; int save_retry = 0; - DNS_REPLY_T *r = NULL; + DNS_REPLY_T *dr = NULL; querybuf reply_buf; unsigned char *reply; + int (*resfunc) __P((const char *, int, int, u_char *, int)); -#define SMRBSIZE sizeof(reply_buf) -#ifndef IP_MAXPACKET -# define IP_MAXPACKET 65535 -#endif +# define SMRBSIZE ((int) sizeof(reply_buf)) +# ifndef IP_MAXPACKET +# define IP_MAXPACKET 65535 +# endif + resfunc = res_search; +# if DNSSEC_TEST + if (tTd(8, 110)) + resfunc = tstdns_search; +# endif + + old_options = _res.options; + _res.options |= options; + if (err != NULL) + *err = 0; + if (herr != NULL) + *herr = 0; if (tTd(8, 16)) { - old_options = _res.options; _res.options |= RES_DEBUG; - sm_dprintf("dns_lookup(%s, %d, %s)\n", domain, - rr_class, dns_type_to_string(rr_type)); + sm_dprintf("dns_lookup_int(%s, %d, %s, %x)\n", domain, + rr_class, dns_type_to_string(rr_type), options); } +# if DNSSEC_TEST + if (tTd(8, 15)) + sm_dprintf("NS=%s, port=%d\n", + inet_ntoa(_res.nsaddr_list[0].sin_addr), + ntohs(_res.nsaddr_list[0].sin_port)); +# endif if (retrans > 0) { save_retrans = _res.retrans; @@ -426,38 +1331,61 @@ dns_lookup_int(domain, rr_class, rr_type, retrans, retry) errno = 0; SM_SET_H_ERRNO(0); reply = (unsigned char *)&reply_buf; - len = res_search(domain, rr_class, rr_type, reply, SMRBSIZE); + len = (*resfunc)(domain, rr_class, rr_type, reply, SMRBSIZE); if (len >= SMRBSIZE) { if (len >= IP_MAXPACKET) { if (tTd(8, 4)) sm_dprintf("dns_lookup: domain=%s, length=%d, default_size=%d, max=%d, status=response too long\n", - domain, len, (int) SMRBSIZE, - IP_MAXPACKET); + domain, len, SMRBSIZE, IP_MAXPACKET); } else { if (tTd(8, 6)) sm_dprintf("dns_lookup: domain=%s, length=%d, default_size=%d, max=%d, status=response longer than default size, resizing\n", - domain, len, (int) SMRBSIZE, - IP_MAXPACKET); + domain, len, SMRBSIZE, IP_MAXPACKET); reply = (unsigned char *)sm_malloc(IP_MAXPACKET); if (reply == NULL) SM_SET_H_ERRNO(TRY_AGAIN); else - len = res_search(domain, rr_class, rr_type, + { + SM_SET_H_ERRNO(0); + len = (*resfunc)(domain, rr_class, rr_type, reply, IP_MAXPACKET); + } } } - if (tTd(8, 16)) + _res.options = old_options; + if (len < 0) + { + if (err != NULL) + *err = errno; + if (herr != NULL) + *herr = h_errno; + if (tTd(8, 16)) + { + sm_dprintf("dns_lookup_int(%s, %d, %s, %x)=%d, errno=%d, h_errno=%d" +# if DNSSEC_TEST + " (%s)" +# endif + "\n", + domain, rr_class, dns_type_to_string(rr_type), + options, len, errno, h_errno +# if DNSSEC_TEST + , herrno2txt(h_errno) +# endif + ); + } + } + else if (tTd(8, 16)) { - _res.options = old_options; - sm_dprintf("dns_lookup(%s, %d, %s) --> %d\n", - domain, rr_class, dns_type_to_string(rr_type), len); + sm_dprintf("dns_lookup_int(%s, %d, %s, %x)=%d\n", + domain, rr_class, dns_type_to_string(rr_type), + options, len); } if (len >= 0 && len < IP_MAXPACKET && reply != NULL) - r = parse_dns_reply(reply, len); + dr = parse_dns_reply(reply, len, flags); if (reply != (unsigned char *)&reply_buf && reply != NULL) { sm_free(reply); @@ -467,29 +1395,209 @@ dns_lookup_int(domain, rr_class, rr_type, retrans, retry) _res.retrans = save_retrans; if (retry > 0) _res.retry = save_retry; - return r; + return dr; } -# if 0 +/* +** DNS_LOOKUP_MAP -- perform DNS map lookup +** +** Parameters: +** domain -- name to lookup +** rr_class -- resource record class +** rr_type -- resource record type +** retrans -- retransmission timeout +** retry -- number of retries +** options -- DNS resolver options +** +** Returns: +** result of lookup if succeeded. +** NULL otherwise. +*/ + DNS_REPLY_T * -dns_lookup(domain, type_name, retrans, retry) +dns_lookup_map(domain, rr_class, rr_type, retrans, retry, options) const char *domain; - const char *type_name; + int rr_class; + int rr_type; time_t retrans; int retry; + unsigned int options; { - int type; + return dns_lookup_int(domain, rr_class, rr_type, retrans, retry, + options, RR_AS_TEXT, NULL, NULL); +} - type = dns_string_to_type(type_name); - if (type == -1) +# if DANE +/* +** DNS2HE -- convert DNS_REPLY_T list to hostent struct +** +** Parameters: +** dr -- DNS lookup result +** family -- address family +** +** Returns: +** hostent struct if succeeded. +** NULL otherwise. +** +** Note: +** this returns a pointer to a static struct! +*/ + +struct hostent * +dns2he(dr, family) + DNS_REPLY_T *dr; + int family; +{ +# define SM_MAX_ADDRS 256 + static struct hostent he; + static char *he_aliases[1]; + static char *he_addr_list[SM_MAX_ADDRS]; +# ifdef IN6ADDRSZ +# define IN_ADDRSZ IN6ADDRSZ +# else +# define IN_ADDRSZ INADDRSZ +# endif + static char he_addrs[SM_MAX_ADDRS * IN_ADDRSZ]; + static char he_name[MAXNAME]; + static bool he_init = false; + struct hostent *h; + struct in_addr ia; + int i; + size_t sz; +# if NETINET6 && DNSSEC_TEST + struct in6_addr ia6; + char buf6[INET6_ADDRSTRLEN]; +# endif + RESOURCE_RECORD_T *rr; + + if (dr == NULL) + return NULL; + + h = &he; + if (!he_init) { - if (tTd(8, 16)) - sm_dprintf("dns_lookup: unknown resource type: `%s'\n", - type_name); + he_aliases[0] = NULL; + he.h_aliases = he_aliases; + he.h_addr_list = he_addr_list; + he.h_name = he_name; + he_init = true; + } + h->h_addrtype = family; + + if (tTd(8, 17)) + sm_dprintf("dns2he: ad=%d\n", dr->dns_r_h.ad); + + /* do we want/need to copy the name? */ + rr = dr->dns_r_head; + if (rr != NULL && rr->rr_domain != NULL) + sm_strlcpy(h->h_name, rr->rr_domain, sizeof(he_name)); + else + h->h_name[0] = '\0'; + + sz = 0; +# if NETINET + if (family == AF_INET) + sz = INADDRSZ; +# endif +# if NETINET6 + if (family == AF_INET6) + sz = IN6ADDRSZ; +# endif + if (sz == 0) return NULL; + h->h_length = sz; + + for (rr = dr->dns_r_head, i = 0; rr != NULL && i < SM_MAX_ADDRS - 1; + rr = rr->rr_next) + { + h->h_addr_list[i] = he_addrs + i * h->h_length; + switch (rr->rr_type) + { +# if NETINET + case T_A: + if (family != AF_INET) + continue; + memmove(h->h_addr_list[i], rr->rr_u.rr_a, INADDRSZ); + ++i; + break; +# endif /* NETINET */ +# if NETINET6 + case T_AAAA: + if (family != AF_INET6) + continue; + memmove(h->h_addr_list[i], rr->rr_u.rr_aaaa, IN6ADDRSZ); + ++i; + break; +# endif /* NETINET6 */ + case T_CNAME: +# if DNSSEC_TEST + if (tTd(8, 16)) + sm_dprintf("dns2he: cname: %s ttl=%d\n", + rr->rr_u.rr_txt, rr->rr_ttl); +# endif + break; + case T_MX: +# if DNSSEC_TEST + if (tTd(8, 16)) + sm_dprintf("dns2he: mx: %d %s ttl=%d\n", + rr->rr_u.rr_mx->mx_r_preference, + rr->rr_u.rr_mx->mx_r_domain, + rr->rr_ttl); +# endif + break; + +# if defined(T_TLSA) + case T_TLSA: +# if DNSSEC_TEST + if (tTd(8, 16)) + { + char *tlsa; + int len; + + len = bin2hex(&tlsa, rr->rr_u.rr_data, + rr->rr_size, 4); + if (len > 0) + sm_dprintf("dns2he: tlsa: %s ttl=%d\n", + tlsa, rr->rr_ttl); + } +# endif + break; +# endif /* T_TLSA */ + } + } + + /* complain if list is too long! */ + SM_ASSERT(i < SM_MAX_ADDRS); + h->h_addr_list[i] = NULL; + +# if DNSSEC_TEST + if (tTd(8, 16)) + { + for (i = 0; h->h_addr_list[i] != NULL && i < SM_MAX_ADDRS; i++) + { + char *addr; + + addr = NULL; +# if NETINET6 + if (h->h_addrtype == AF_INET6) + { + memmove(&ia6, h->h_addr_list[i], IN6ADDRSZ); + addr = anynet_ntop(&ia6, buf6, sizeof(buf6)); + } + else +# endif /* NETINET6 */ + /* "else" in #if code above */ + { + memmove(&ia, h->h_addr_list[i], INADDRSZ); + addr = (char *) inet_ntoa(ia); + } + if (addr != NULL) + sm_dprintf("dns2he: addr[%d]: %s\n", i, addr); + } } - return dns_lookup_int(domain, C_IN, type, retrans, retry); +# endif /* DNSSEC_TEST */ + return h; } -# endif /* 0 */ +# endif /* DANE */ # endif /* NAMED_BIND */ -#endif /* DNSMAP */ +#endif /* DNSMAP || DANE */ |
