Gregory Neil Shapiro 5b0945b570 Merge sendmail 8.16.1 to HEAD: See contrib/sendmail/RELEASE_NOTES for details
Includes build infrastructure & config updates required for changes in 8.16.1

MFC after:	5 days
2020-07-15 18:28:54 +00:00

1604 lines
33 KiB
C

/*
* 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>
#if DNSMAP || DANE
# if NAMED_BIND
# if NETINET
# include <netinet/in_systm.h>
# include <netinet/ip.h>
# 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
{
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_strcasecmp(name, p->st_name) == 0)
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>
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;
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
** 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 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_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];
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)
{
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);
}
}
# endif /* DNSSEC_TEST */
return h;
}
# endif /* DANE */
# endif /* NAMED_BIND */
#endif /* DNSMAP || DANE */