summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/libc/net/Makefile.inc11
-rw-r--r--lib/libc/net/getrrsetbyname.3149
-rw-r--r--lib/libc/net/getrrsetbyname.c506
-rw-r--r--lib/libc/shlib_version2
4 files changed, 662 insertions, 6 deletions
diff --git a/lib/libc/net/Makefile.inc b/lib/libc/net/Makefile.inc
index 641d6121051..805ae5d2247 100644
--- a/lib/libc/net/Makefile.inc
+++ b/lib/libc/net/Makefile.inc
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile.inc,v 1.30 2000/02/23 15:39:53 itojun Exp $
+# $OpenBSD: Makefile.inc,v 1.31 2001/08/06 14:40:47 jakob Exp $
# net sources
.PATH: ${LIBCSRCDIR}/arch/${MACHINE_ARCH}/net ${LIBCSRCDIR}/net
@@ -8,8 +8,8 @@ CFLAGS+=-DRESOLVSORT
SRCS+= base64.c freeaddrinfo.c gai_strerror.c getaddrinfo.c gethostnamadr.c \
getifaddrs.c getnameinfo.c getnetbyaddr.c getnetbyname.c getnetent.c \
getnetnamadr.c getproto.c getprotoent.c getprotoname.c \
- getservbyname.c getservbyport.c getservent.c herror.c \
- if_indextoname.c if_nameindex.c if_nametoindex.c inet_addr.c \
+ getservbyname.c getservbyport.c getservent.c getrrsetbyname.c \
+ herror.c if_indextoname.c if_nameindex.c if_nametoindex.c inet_addr.c \
inet_lnaof.c inet_makeaddr.c inet_neta.c inet_netof.c inet_network.c \
inet_net_ntop.c inet_net_pton.c inet_ntoa.c inet_ntop.c inet_pton.c \
ipx_addr.c ipx_ntoa.c iso_addr.c linkaddr.c ns_addr.c ns_ntoa.c \
@@ -32,8 +32,8 @@ MAN+= byteorder.3 ethers.3 getaddrinfo.3 gethostbyname.3 getifaddrs.3 \
getnameinfo.3 getnetent.3 getprotoent.3 getservent.3 inet.3 \
if_indextoname.3 inet_net.3 iso_addr.3 link_addr.3 ns.3 ipx.3 \
rcmd.3 rcmdsh.3 resolver.3 net_addrcmp.3 \
- inet6_option_space.3 inet6_rthdr_space.3
-
+ inet6_option_space.3 inet6_rthdr_space.3 \
+ getrrsetbyname.3
MLINKS+=byteorder.3 htonl.3 byteorder.3 htons.3 byteorder.3 ntohl.3 \
byteorder.3 ntohs.3 byteorder.3 htobe16.3 byteorder.3 htobe32.3 \
@@ -82,3 +82,4 @@ MLINKS+=inet6_rthdr_space.3 inet6_rthdr_init.3 \
inet6_rthdr_space.3 inet6_rthdr_segments.3 \
inet6_rthdr_space.3 inet6_rthdr_getaddr.3 \
inet6_rthdr_space.3 inet6_rthdr_getflags.3
+MLINKS+=getrrsetbyname.3 freerrset.3
diff --git a/lib/libc/net/getrrsetbyname.3 b/lib/libc/net/getrrsetbyname.3
new file mode 100644
index 00000000000..3b8c06999ec
--- /dev/null
+++ b/lib/libc/net/getrrsetbyname.3
@@ -0,0 +1,149 @@
+.\" $Id: getrrsetbyname.3,v 1.1 2001/08/06 14:40:47 jakob Exp $
+.\"
+.\" Copyright (C) 2000, 2001 Internet Software Consortium.
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
+.\" DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+.\" INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+.\" FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+.\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+.\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd Oct 18, 2000
+.Dt GETRRSETBYNAME 3
+.Os
+.Sh NAME
+.Nm getrrsetbyname
+.Nd retrieve DNS records
+.Sh SYNOPSIS
+.Fd #include <netdb.h>
+.Ft int
+.Fn getrrsetbyname "const char *hostname" "unsigned int rdclass" \
+"unsigned int rdtype" "unsigned int flags" "struct rrsetinfo **res"
+.Ft int
+.Fn freerrset "struct rrsetinfo **rrset"
+.Sh DESCRIPTION
+.Fn getrrsetbyname
+gets a set of resource records associated with a
+.Fa hostname ,
+.Fa class
+and
+.Fa type .
+.Fa hostname
+is a pointer a to null-terminated string.
+The
+.Fa flags
+field is currently unused and must be zero.
+.Pp
+After a successful call to
+.Fn getrrsetbyname ,
+.Fa *res
+is a pointer to an
+.Li rrsetinfo
+structure, containing a list of one or more
+.Li rdatainfo
+structures containing resource records and potentially another list of
+.Li rdatainfo
+structures containing SIG resource records associated with those records.
+The members
+.Li rri_rdclass
+and
+.Li rri_rdtype
+are copied from the parameters.
+.Li rri_ttl
+and
+.Li rri_name
+are properties of the obtained rrset.
+The resource records contained in
+.Li rri_rdatas
+and
+.Li rri_sigs
+are in uncompressed DNS wire format.
+Properties of the rdataset are represented in the
+.Li rri_flags
+bitfield. If the
+.Dv RRSET_VALIDATED
+bit is set, the data has been DNSSEC
+validated and the signatures verified.
+.Pp
+The following structures are used:
+.Bd -literal -offset
+struct rdatainfo {
+ unsigned int rdi_length; /* length of data */
+ unsigned char *rdi_data; /* record data */
+};
+
+struct rrsetinfo {
+ unsigned int rri_flags; /* RRSET_VALIDATED ... */
+ unsigned int rri_rdclass; /* class number */
+ unsigned int rri_rdtype; /* RR type number */
+ unsigned int rri_ttl; /* time to live */
+ unsigned int rri_nrdatas; /* size of rdatas array */
+ unsigned int rri_nsigs; /* size of sigs array */
+ char *rri_name; /* canonical name */
+ struct rdatainfo *rri_rdatas; /* individual records */
+ struct rdatainfo *rri_sigs; /* individual signatures */
+};
+.Ed
+.Pp
+All of the information returned by
+.Fn getrrsetbyname
+is dynamically allocated: the
+.Li rrsetinfo
+and
+.Li rdatainfo
+structures,
+and the canonical host name strings pointed to by the
+.Li rrsetinfostructure.
+Memory allocated for the dynamically allocated structures created by
+a successful call to
+.Fn getrrsetbyname
+is released by
+.Fn freerrset .
+.Li rrset
+is a pointer to a
+.Li struct rrset
+created by a call to
+.Fn getrrsetbyname .
+.Pp
+If the EDNS0 option is activated in
+.Xr resolv.conf 3 ,
+.Fn getrrsetbyname
+will request DNSSEC authentication using the EDNS0 DNSSEC OK (DO) bit.
+.Sh "RETURN VALUES"
+.Fn getrrsetbyname
+returns zero on success, and one of the following error
+codes if an error occurred:
+.Pp
+.Bl -tag -width ERRSET_NOMEMORY -compact
+.It Dv ERRSET_NONAME
+the name does not exist
+.It Dv ERRSET_NODATA
+the name exists, but does not have data of the desired type
+.It Dv ERRSET_NOMEMORY
+memory could not be allocated
+.It Dv ERRSET_INVAL
+a parameter is invalid
+.It Dv ERRSET_FAIL
+other failure
+.El
+.Sh SEE ALSO
+.Xr resolver 3 ,
+.Xr resolv.conf 5 ,
+.Xr named 8
+.Sh HISTORY
+.Nm
+first appeared in
+.Ox 3.0 .
+.Sh BUGS
+The data in
+.Li *rdi_data
+should be returned in uncompressed wire format.
+Currently, the data is in compressed format and the caller can't
+uncompress since it doesn't have the full message.
diff --git a/lib/libc/net/getrrsetbyname.c b/lib/libc/net/getrrsetbyname.c
new file mode 100644
index 00000000000..cacf9157e78
--- /dev/null
+++ b/lib/libc/net/getrrsetbyname.c
@@ -0,0 +1,506 @@
+/* $Id: getrrsetbyname.c,v 1.1 2001/08/06 14:40:47 jakob Exp $ */
+
+/*
+ * Copyright (c) 2001 Jakob Schlyter. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+/*
+ * Portions Copyright (c) 1999-2001 Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ANSWER_BUFFER_SIZE 1024*64
+
+
+struct dns_query {
+ char *name;
+ u_int16_t type;
+ u_int16_t class;
+ struct dns_query *next;
+};
+
+struct dns_rr {
+ char *name;
+ u_int16_t type;
+ u_int16_t class;
+ u_int16_t ttl;
+ u_int16_t size;
+ void *rdata;
+ struct dns_rr *next;
+};
+
+struct dns_response {
+ HEADER header;
+ struct dns_query *query;
+ struct dns_rr *answer;
+ struct dns_rr *authority;
+ struct dns_rr *additional;
+};
+
+static struct dns_response *parse_dns_response(const char *, int);
+static struct dns_query *parse_dns_qsection(const char *, int, const char **,
+ int);
+static struct dns_rr *parse_dns_rrsection(const char *, int, const char **,
+ int);
+
+static void free_dns_query(struct dns_query *);
+static void free_dns_rr(struct dns_rr *);
+static void free_dns_response(struct dns_response *);
+
+static int count_dns_rr(struct dns_rr *, u_int16_t, u_int16_t);
+
+int
+getrrsetbyname(const char *hostname, unsigned int rdclass,
+ unsigned int rdtype, unsigned int flags,
+ struct rrsetinfo **res)
+{
+ int result;
+ struct rrsetinfo *rrset = NULL;
+ struct dns_response *response;
+ struct dns_rr *rr;
+ struct rdatainfo *rdata;
+ unsigned length, index_ans, index_sig;
+ char answer[ANSWER_BUFFER_SIZE];
+
+ /* check for invalid class and type */
+ if (rdclass > 0xffff || rdtype > 0xffff) {
+ result = ERRSET_INVAL;
+ goto fail;
+ }
+
+ /* don't allow queries of class or type ANY */
+ if (rdclass == 0xff || rdtype == 0xff) {
+ result = ERRSET_INVAL;
+ goto fail;
+ }
+
+ /* don't allow flags yet, unimplemented */
+ if (flags) {
+ result = ERRSET_INVAL;
+ goto fail;
+ }
+
+ /* initialize resolver */
+ if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
+ result = ERRSET_FAIL;
+ goto fail;
+ }
+
+#ifdef DEBUG
+ _res.options |= RES_DEBUG;
+#endif /* DEBUG */
+
+#ifdef RES_USE_DNSSEC
+ /* turn on DNSSEC if EDNS0 is configured */
+ if (_res.options & RES_USE_EDNS0)
+ _res.options |= RES_USE_DNSSEC;
+#endif /* RES_USE_DNSEC */
+
+ /* make query */
+ length = res_query(hostname, rdclass, rdtype, answer, sizeof(answer));
+ if (length < 0) {
+ switch(h_errno) {
+ case HOST_NOT_FOUND:
+ result = ERRSET_NONAME;
+ goto fail;
+ case NO_DATA:
+ result = ERRSET_NODATA;
+ goto fail;
+ default:
+ result = ERRSET_FAIL;
+ goto fail;
+ }
+ }
+
+ /* parse result */
+ response = parse_dns_response(answer, length);
+ if (response == NULL) {
+ result = ERRSET_FAIL;
+ goto fail;
+ }
+
+ if (response->header.qdcount != 1 ) {
+ result = ERRSET_FAIL;
+ goto fail;
+ }
+
+ /* initialize rrset */
+ rrset = calloc(1, sizeof(struct rrsetinfo));
+ if (rrset == NULL) {
+ result = ERRSET_NOMEMORY;
+ goto fail;
+ }
+ rrset->rri_rdclass = response->query->class;
+ rrset->rri_rdtype = response->query->type;
+ rrset->rri_ttl = response->answer->ttl;
+ rrset->rri_nrdatas = response->header.ancount;
+
+ /* check for authenticated data */
+ if (response->header.ad == 1)
+ rrset->rri_flags |= RRSET_VALIDATED;
+
+ /* copy name from answer section */
+ length = strlen(response->answer->name);
+ rrset->rri_name = malloc(length + 1);
+ if (rrset->rri_name == NULL) {
+ result = ERRSET_NOMEMORY;
+ goto fail;
+ }
+ strlcpy(rrset->rri_name, response->answer->name, length + 1);
+
+ /* count answers */
+ rrset->rri_nrdatas = count_dns_rr(response->answer, rrset->rri_rdclass,
+ rrset->rri_rdtype);
+ rrset->rri_nsigs = count_dns_rr(response->answer, rrset->rri_rdclass,
+ T_SIG);
+
+ /* allocate memory for answers */
+ rrset->rri_rdatas = calloc(rrset->rri_nrdatas,
+ sizeof(struct rdatainfo));
+ if (rrset->rri_rdatas == NULL) {
+ result = ERRSET_NOMEMORY;
+ goto fail;
+ }
+
+ /* allocate memory for signatures */
+ rrset->rri_sigs = calloc(rrset->rri_nsigs, sizeof(struct rdatainfo));
+ if (rrset->rri_sigs == NULL) {
+ result = ERRSET_NOMEMORY;
+ goto fail;
+ }
+
+ /* copy answers & signatures */
+ for (rr = response->answer, index_ans = 0, index_sig = 0;
+ rr; rr = rr->next) {
+
+ rdata = NULL;
+
+ if (rr->class == rrset->rri_rdclass &&
+ rr->type == rrset->rri_rdtype)
+ rdata = &rrset->rri_rdatas[index_ans++];
+
+ if (rr->class == rrset->rri_rdclass &&
+ rr->type == T_SIG)
+ rdata = &rrset->rri_sigs[index_sig++];
+
+ if (rdata) {
+ rdata->rdi_length = rr->size;
+ rdata->rdi_data = malloc(rr->size);
+
+ if (rdata->rdi_data == NULL) {
+ result = ERRSET_NOMEMORY;
+ goto fail;
+ }
+ memcpy(rdata->rdi_data, rr->rdata, rr->size);
+ }
+ }
+
+ *res = rrset;
+ return (ERRSET_SUCCESS);
+
+fail:
+ if (rrset != NULL)
+ freerrset(rrset);
+ return (result);
+}
+
+void
+freerrset(struct rrsetinfo *rrset)
+{
+ u_int16_t i;
+
+ if (rrset == NULL) {
+ return;
+ }
+
+ for (i = 0; i < rrset->rri_nrdatas; i++) {
+ if (rrset->rri_rdatas[i].rdi_data == NULL)
+ break;
+ free(rrset->rri_rdatas[i].rdi_data);
+ }
+ free(rrset->rri_rdatas);
+
+ for (i = 0; i < rrset->rri_nsigs; i++) {
+ if (rrset->rri_sigs[i].rdi_data == NULL)
+ break;
+ free(rrset->rri_sigs[i].rdi_data);
+ }
+ free(rrset->rri_sigs);
+
+ free(rrset->rri_name);
+
+ free(rrset);
+}
+
+
+/*
+ * DNS response parsing routines
+ */
+
+static struct dns_response *
+parse_dns_response(const char *answer, int size)
+{
+ struct dns_response *resp;
+ const char *cp;
+
+ /* allocate memory for the response */
+ resp = malloc(sizeof(*resp));
+ if (resp == NULL)
+ return (NULL);
+
+ /* initialize current pointer */
+ cp = answer;
+
+ /* copy header */
+ memcpy(&resp->header, cp, HFIXEDSZ);
+ cp += HFIXEDSZ;
+
+ /* fix header byte order */
+ resp->header.qdcount = ntohs(resp->header.qdcount);
+ resp->header.ancount = ntohs(resp->header.ancount);
+ resp->header.nscount = ntohs(resp->header.nscount);
+ resp->header.arcount = ntohs(resp->header.arcount);
+
+ /* there must be at least one query */
+ if (resp->header.qdcount < 1) {
+ free_dns_response(resp);
+ return (NULL);
+ }
+
+ /* parse query section */
+ resp->query = parse_dns_qsection(answer, size, &cp,
+ resp->header.qdcount);
+ if (resp->header.qdcount && resp->query == NULL) {
+ free_dns_response(resp);
+ return (NULL);
+ }
+
+ /* parse answer section */
+ resp->answer = parse_dns_rrsection(answer, size, &cp,
+ resp->header.ancount);
+ if (resp->header.ancount && resp->answer == NULL) {
+ free_dns_response(resp);
+ return (NULL);
+ }
+
+ /* parse authority section */
+ resp->authority = parse_dns_rrsection(answer, size, &cp,
+ resp->header.nscount);
+ if (resp->header.nscount && resp->authority == NULL) {
+ free_dns_response(resp);
+ return (NULL);
+ }
+
+ /* parse additional section */
+ resp->additional = parse_dns_rrsection(answer, size, &cp,
+ resp->header.arcount);
+ if (resp->header.arcount && resp->additional == NULL) {
+ free_dns_response(resp);
+ return (NULL);
+ }
+
+ return (resp);
+}
+
+static struct dns_query *
+parse_dns_qsection(const char *answer, int size, const char **cp, int count)
+{
+ struct dns_query *head, *curr, *prev;
+ int i, length;
+ char name[MAXDNAME];
+
+ for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) {
+
+ /* allocate and initialize struct */
+ curr = calloc(1, sizeof(struct dns_query));
+ if (curr == NULL) {
+ free_dns_query(head);
+ return (NULL);
+ }
+ if (head == NULL)
+ head = curr;
+ if (prev != NULL)
+ prev->next = curr;
+
+ /* name */
+ length = dn_expand(answer, answer + size, *cp, name,
+ sizeof(name));
+ if (length < 0) {
+ free_dns_query(head);
+ return (NULL);
+ }
+ curr->name = strdup(name);
+ if (curr->name == NULL) {
+ free_dns_query(head);
+ return (NULL);
+ }
+ *cp += length;
+
+ /* type */
+ curr->type = _getshort(*cp);
+ *cp += INT16SZ;
+
+ /* class */
+ curr->class = _getshort(*cp);
+ *cp += INT16SZ;
+ }
+
+ return (head);
+}
+
+static struct dns_rr *
+parse_dns_rrsection(const char *answer, int size, const char **cp, int count)
+{
+ struct dns_rr *head, *curr, *prev;
+ int i, length;
+ char name[MAXDNAME];
+
+ for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) {
+
+ /* allocate and initialize struct */
+ curr = calloc(1, sizeof(struct dns_rr));
+ if (curr == NULL) {
+ free_dns_rr(head);
+ return (NULL);
+ }
+ if (head == NULL)
+ head = curr;
+ if (prev != NULL)
+ prev->next = curr;
+
+ /* name */
+ length = dn_expand(answer, answer + size, *cp, name,
+ sizeof(name));
+ if (length < 0) {
+ free_dns_rr(head);
+ return (NULL);
+ }
+ curr->name = strdup(name);
+ if (curr->name == NULL) {
+ free_dns_rr(head);
+ return (NULL);
+ }
+ *cp += length;
+
+ /* type */
+ curr->type = _getshort(*cp);
+ *cp += INT16SZ;
+
+ /* class */
+ curr->class = _getshort(*cp);
+ *cp += INT16SZ;
+
+ /* ttl */
+ curr->ttl = _getlong(*cp);
+ *cp += INT32SZ;
+
+ /* rdata size */
+ curr->size = _getshort(*cp);
+ *cp += INT16SZ;
+
+ /* rdata itself */
+ curr->rdata = malloc(curr->size);
+ if (curr->rdata == NULL) {
+ free_dns_rr(head);
+ return (NULL);
+ }
+ memcpy(curr->rdata, *cp, curr->size);
+ *cp += curr->size;
+ }
+
+ return (head);
+}
+
+
+static void
+free_dns_query(struct dns_query *p)
+{
+ if (p == NULL)
+ return;
+
+ free(p->name);
+ free_dns_query(p->next);
+ free(p);
+}
+
+static void
+free_dns_rr(struct dns_rr *p)
+{
+ if (p == NULL)
+ return;
+
+ free(p->name);
+ free(p->rdata);
+ free_dns_rr(p->next);
+ free(p);
+}
+
+static void
+free_dns_response(struct dns_response *p)
+{
+ if (p == NULL)
+ return;
+
+ free_dns_query(p->query);
+ free_dns_rr(p->answer);
+ free_dns_rr(p->authority);
+ free_dns_rr(p->additional);
+ free(p);
+}
+
+static int
+count_dns_rr(struct dns_rr *p, u_int16_t class, u_int16_t type)
+{
+ int n = 0;
+
+ while(p) {
+ if (p->class == class && p->type == type)
+ n++;
+ p = p->next;
+ };
+
+ return (n);
+}
diff --git a/lib/libc/shlib_version b/lib/libc/shlib_version
index 544b4b21d49..b9c68890d55 100644
--- a/lib/libc/shlib_version
+++ b/lib/libc/shlib_version
@@ -1,2 +1,2 @@
major=27
-minor=2
+minor=3