/* * 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 * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. * */ /* * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska H�gskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <sendmail.h> #include <sm/sendmail.h> #if NAMED_BIND # if NETINET # include <netinet/in_systm.h> # include <netinet/ip.h> # endif # if DNSSEC_TEST || _FFR_NAMESERVER # define _DEFINE_SMR_GLOBALS 1 # endif # include "sm_resolve.h" # if DNSMAP || DANE #include <arpa/inet.h> SM_RCSID("$Id: sm_resolve.c,v 8.40 2013-11-22 20:51:56 ca Exp $") static struct stot { const char *st_name; int st_type; } stot[] = { # if NETINET { "A", T_A }, # endif # if NETINET6 { "AAAA", T_AAAA }, # endif { "NS", T_NS }, { "CNAME", T_CNAME }, { "PTR", T_PTR }, { "MX", T_MX }, { "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, 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 ** ** Parameters: ** name -- name of resource record type ** ** Returns: ** type if succeeded. ** -1 otherwise. */ int dns_string_to_type(name) const char *name; { struct stot *p = stot; for (p = stot; p->st_name != NULL; p++) if (SM_STRCASEEQ(name, p->st_name)) return p->st_type; return -1; } /* ** DNS_TYPE_TO_STRING -- convert resource record type into name ** ** Parameters: ** type -- resource record type ** ** Returns: ** name if succeeded. ** NULL otherwise. */ const char * dns_type_to_string(type) int type; { struct stot *p = stot; for (p = stot; p->st_name != NULL; p++) if (type == p->st_type) return p->st_name; return NULL; } /* ** DNS_FREE_DATA -- free all components of a DNS_REPLY_T ** ** Parameters: ** dr -- pointer to DNS_REPLY_T ** ** Returns: ** none. */ void dns_free_data(dr) DNS_REPLY_T *dr; { RESOURCE_RECORD_T *rr; 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; if (rr->rr_domain != NULL) sm_free(rr->rr_domain); if (rr->rr_u.rr_data != NULL) sm_free(rr->rr_u.rr_data); rr = rr->rr_next; sm_free(tmp); } 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; } /* ** PARSE_DNS_REPLY -- parse DNS reply data. ** ** 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, 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 *dr; RESOURCE_RECORD_T **rr; 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(dr, 0, sizeof(*dr)); p = data; /* doesn't work on Crays? */ 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) 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) 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(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) { int type, class, ttl, size, txtlen; status = dn_expand(data, data + len, p, host, sizeof(host)); if (status < 0) goto error; ++ui; p += status; GETSHORT(type, p); GETSHORT(class, p); GETLONG(ttl, p); GETSHORT(size, p); if (p + size > data + len) { /* ** announced size of data exceeds length of ** data paket: someone is cheating. */ if (LogLevel > 5) sm_syslog(LOG_WARNING, NOQID, "ERROR: DNS RDLENGTH=%d > data len=%d", size, len - (int)(p - data)); goto error; } *rr = (RESOURCE_RECORD_T *) sm_malloc(sizeof(**rr)); if (*rr == NULL) goto error; memset(*rr, 0, sizeof(**rr)); (*rr)->rr_domain = sm_strdup(host); if ((*rr)->rr_domain == NULL) goto error; (*rr)->rr_type = type; (*rr)->rr_class = class; (*rr)->rr_ttl = ttl; (*rr)->rr_size = size; switch (type) { case T_NS: case T_CNAME: case T_PTR: status = dn_expand(data, data + len, p, host, sizeof(host)); if (status < 0) 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) goto error; break; case T_MX: case T_AFSDB: status = dn_expand(data, data + len, p + 2, host, sizeof(host)); if (status < 0) 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) 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) 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) 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]; (void) sm_strlcpy((*rr)->rr_u.rr_srv->srv_r_target, host, l); break; case T_TXT: /* ** The TXT record contains the length as ** leading byte, hence the value is restricted ** to 255, which is less than the maximum value ** of RDLENGTH (size). Nevertheless, txtlen ** must be less than size because the latter ** specifies the length of the entire TXT ** record. */ txtlen = *p; if (txtlen >= size) { if (LogLevel > 5) sm_syslog(LOG_WARNING, NOQID, "ERROR: DNS TXT record size=%d <= text len=%d", size, txtlen); goto error; } (*rr)->rr_u.rr_txt = (char *) sm_malloc(txtlen + 1); if ((*rr)->rr_u.rr_txt == 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) { 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)); } break; } p += size; rr = &(*rr)->rr_next; } *rr = NULL; return dr; error: dns_free_data(dr); return NULL; } # if DNSSEC_TEST # include <arpa/nameser.h> # if _FFR_8BITENVADDR # include <sm/sendmail.h> # endif 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"; } /* ** 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; #define DN_COMP_CHK do \ { \ if (n < 0) \ { \ if (tTd(8, 91)) \ sm_dprintf("gen_dns_reply: dn_comp=%d\n", n); \ return n; \ } \ } while (0) 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); DN_COMP_CHK; cp += n; PUTSHORT(qtype, cp); PUTSHORT(class, cp); } hp->ad = ad; if (ep - cp < QFIXEDSZ) { if (tTd(8, 91)) sm_dprintf("gen_dns_reply: ep-cp=%ld\n", (long) (ep - cp)); return (-1); } n = dn_comp(domain, cp, ep - cp - QFIXEDSZ, dnptrs, lastdnptr); DN_COMP_CHK; 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); DN_COMP_CHK; 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); DN_COMP_CHK; 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 (SM_IS_EMPTY(str)) 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 (SM_IS_EMPTY(str)) return 0; #define TTL_PRE "ttl=" if (strstr(str, TTL_PRE) == NULL) return 0; return strtoul(str + strlen(TTL_PRE), NULL, 10); } # 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 (SM_IS_EMPTY(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, rlen=%d\n", token, pref, rlen); } # 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; # if _FFR_8BITENVADDR char qbuf[MAXNAME_I]; char *qdomain; # else # define qdomain domain # endif char key[MAXNAME_I + 16]; char rhs[MAXLINE]; unsigned char *anspos; rlen = -1; herr = 0; if (class != C_IN) goto error; if (SM_IS_EMPTY(domain)) goto error; tag = rr_type2tag(type); if (tag == NULL) goto error; maprcode = EX_OK; ad = -1; flags = 0; # if _FFR_8BITENVADDR if (tTd(8, 62)) sm_dprintf("domain=%s\n", domain); (void) dequote_internal_chars((char *)domain, qbuf, sizeof(qbuf)); query = qbuf; qdomain = qbuf; if (tTd(8, 63)) sm_dprintf("qdomain=%s\n", qdomain); # else query = domain; # endif /* _FFR_8BITENVADDR */ 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(qdomain); \ av[0] = key; \ av[1] = NULL; \ snprintf(key, sizeof(key), "%s:%s", tag, qdomain); \ p = (*map->s_map.map_class->map_lookup)(&map->s_map, key, av, \ &maprcode); \ if (p != NULL) \ break; \ if (!tTd(8, 112) || (len > 0 && '.' == qdomain[len - 1])) \ break; \ snprintf(key, sizeof(key), "%s:%s.", tag, qdomain); \ 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(qdomain, 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') qdomain = last + 1; else qdomain = 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, qdomain, 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(qdomain, 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_I]; int len; if (NULL == name) goto error; if (SM_IS_EMPTY(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 look up ** rr_class -- resource record class ** 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. ** NULL otherwise. */ DNS_REPLY_T * 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 *dr = NULL; querybuf reply_buf; unsigned char *reply; int (*resfunc) __P((const char *, int, int, u_char *, int)); # 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)) { _res.options |= RES_DEBUG; 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; _res.retrans = retrans; } if (retry > 0) { save_retry = _res.retry; _res.retry = retry; } errno = 0; SM_SET_H_ERRNO(0); reply = (unsigned char *)&reply_buf; 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, 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, SMRBSIZE, IP_MAXPACKET); reply = (unsigned char *)sm_malloc(IP_MAXPACKET); if (reply == NULL) SM_SET_H_ERRNO(TRY_AGAIN); else { SM_SET_H_ERRNO(0); len = (*resfunc)(domain, rr_class, rr_type, reply, IP_MAXPACKET); } } } _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)) { 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) dr = parse_dns_reply(reply, len, flags); if (reply != (unsigned char *)&reply_buf && reply != NULL) { sm_free(reply); reply = NULL; } if (retrans > 0) _res.retrans = save_retrans; if (retry > 0) _res.retry = save_retry; return dr; } /* ** DNS_LOOKUP_MAP -- perform DNS map lookup ** ** Parameters: ** domain -- name to look up ** 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_map(domain, rr_class, rr_type, retrans, retry, options) const char *domain; int rr_class; int rr_type; time_t retrans; int retry; unsigned int options; { return dns_lookup_int(domain, rr_class, rr_type, retrans, retry, options, RR_AS_TEXT, NULL, NULL); } # 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_I]; static bool he_init = false; struct hostent *h; 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) { 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)) { struct in_addr ia; 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); } } # endif /* DNSSEC_TEST */ return h; } # endif /* DANE */ # endif /* DNSMAP || DANE */ # if DNSSEC_TEST || _FFR_NAMESERVER /* ** DNS_ADDNS -- add one NS in resolver context ** ** Parameters: ** ns -- (IPv4 address of) nameserver ** port -- nameserver port (host order) ** ** Returns: ** None. */ static void dns_addns __P((struct in_addr *, unsigned int)); static int nsidx = 0; #ifndef MAXNS # define MAXNS 3 #endif static void dns_addns(ns, port) struct in_addr *ns; unsigned int port; { if (nsidx >= MAXNS) syserr("too many NameServers defined (%d max)", MAXNS); _res.nsaddr_list[nsidx].sin_family = AF_INET; _res.nsaddr_list[nsidx].sin_addr = *ns; if (port != 0) _res.nsaddr_list[nsidx].sin_port = htons(port); _res.nscount = ++nsidx; if (tTd(8, 61)) sm_dprintf("dns_addns: nsidx=%d, ns=%s:%u\n", nsidx - 1, inet_ntoa(*ns), port); } /* ** 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 (SM_IS_EMPTY(p)) 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_addns(&nsip, port); } if (h != NULL) *h = ' '; return r > 0 ? 0 : -1; } # endif /* DNSSEC_TEST || _FFR_NAMESERVER */ #endif /* NAMED_BIND */