Path: blob/main/lib/libc/resolv/res_findzonecut.c
107228 views
1/*-2* SPDX-License-Identifier: ISC3*4* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")5* Copyright (c) 1999 by Internet Software Consortium.6*7* Permission to use, copy, modify, and distribute this software for any8* purpose with or without fee is hereby granted, provided that the above9* copyright notice and this permission notice appear in all copies.10*11* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES12* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF13* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR14* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES15* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN16* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT17* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.18*/1920/* Import. */2122#include "port_before.h"2324#include <sys/param.h>25#include <sys/socket.h>26#include <sys/time.h>2728#include <netinet/in.h>29#include <arpa/inet.h>30#include <arpa/nameser.h>3132#include <errno.h>33#include <limits.h>34#include <netdb.h>35#include <stdarg.h>36#include <stdio.h>37#include <stdlib.h>38#include <string.h>3940#include <isc/list.h>4142#include "port_after.h"4344#include <resolv.h>4546/* Data structures. */4748typedef struct rr_a {49LINK(struct rr_a) link;50union res_sockaddr_union addr;51} rr_a;52typedef LIST(rr_a) rrset_a;5354typedef struct rr_ns {55LINK(struct rr_ns) link;56const char * name;57unsigned int flags;58rrset_a addrs;59} rr_ns;60typedef LIST(rr_ns) rrset_ns;6162#define RR_NS_HAVE_V4 0x0163#define RR_NS_HAVE_V6 0x026465/* Forward. */6667static int satisfy(res_state, const char *, rrset_ns *,68union res_sockaddr_union *, int);69static int add_addrs(res_state, rr_ns *,70union res_sockaddr_union *, int);71static int get_soa(res_state, const char *, ns_class, int,72char *, size_t, char *, size_t,73rrset_ns *);74static int get_ns(res_state, const char *, ns_class, int, rrset_ns *);75static int get_glue(res_state, ns_class, int, rrset_ns *);76static int save_ns(res_state, ns_msg *, ns_sect,77const char *, ns_class, int, rrset_ns *);78static int save_a(res_state, ns_msg *, ns_sect,79const char *, ns_class, int, rr_ns *);80static void free_nsrrset(rrset_ns *);81static void free_nsrr(rrset_ns *, rr_ns *);82static rr_ns * find_ns(rrset_ns *, const char *);83static int do_query(res_state, const char *, ns_class, ns_type,84u_char *, ns_msg *);85static void res_dprintf(const char *, ...) ISC_FORMAT_PRINTF(1, 2);8687/* Macros. */8889#define DPRINTF(x) do {\90int save_errno = errno; \91if ((statp->options & RES_DEBUG) != 0U) res_dprintf x; \92errno = save_errno; \93} while (0)9495/* Public. */9697/*%98* find enclosing zone for a <dname,class>, and some server addresses99*100* parameters:101*\li res - resolver context to work within (is modified)102*\li dname - domain name whose enclosing zone is desired103*\li class - class of dname (and its enclosing zone)104*\li zname - found zone name105*\li zsize - allocated size of zname106*\li addrs - found server addresses107*\li naddrs - max number of addrs108*109* return values:110*\li < 0 - an error occurred (check errno)111*\li = 0 - zname is now valid, but addrs[] wasn't changed112*\li > 0 - zname is now valid, and return value is number of addrs[] found113*114* notes:115*\li this function calls res_nsend() which means it depends on correctly116* functioning recursive nameservers (usually defined in /etc/resolv.conf117* or its local equivalent).118*119*\li we start by asking for an SOA<dname,class>. if we get one as an120* answer, that just means <dname,class> is a zone top, which is fine.121* more than likely we'll be told to go pound sand, in the form of a122* negative answer.123*124*\li note that we are not prepared to deal with referrals since that would125* only come from authority servers and our correctly functioning local126* recursive server would have followed the referral and got us something127* more definite.128*129*\li if the authority section contains an SOA, this SOA should also be the130* closest enclosing zone, since any intermediary zone cuts would've been131* returned as referrals and dealt with by our correctly functioning local132* recursive name server. but an SOA in the authority section should NOT133* match our dname (since that would have been returned in the answer134* section). an authority section SOA has to be "above" our dname.135*136*\li however, since authority section SOA's were once optional, it's137* possible that we'll have to go hunting for the enclosing SOA by138* ripping labels off the front of our dname -- this is known as "doing139* it the hard way."140*141*\li ultimately we want some server addresses, which are ideally the ones142* pertaining to the SOA.MNAME, but only if there is a matching NS RR.143* so the second phase (after we find an SOA) is to go looking for the144* NS RRset for that SOA's zone.145*146*\li no answer section processed by this code is allowed to contain CNAME147* or DNAME RR's. for the SOA query this means we strip a label and148* keep going. for the NS and A queries this means we just give up.149*/150151#ifndef _LIBC152int153res_findzonecut(res_state statp, const char *dname, ns_class class, int opts,154char *zname, size_t zsize, struct in_addr *addrs, int naddrs)155{156int result, i;157union res_sockaddr_union *u;158159160opts |= RES_IPV4ONLY;161opts &= ~RES_IPV6ONLY;162163u = calloc(naddrs, sizeof(*u));164if (u == NULL)165return(-1);166167result = res_findzonecut2(statp, dname, class, opts, zname, zsize,168u, naddrs);169170for (i = 0; i < result; i++) {171addrs[i] = u[i].sin.sin_addr;172}173free(u);174return (result);175}176#endif177178int179res_findzonecut2(res_state statp, const char *dname, ns_class class, int opts,180char *zname, size_t zsize, union res_sockaddr_union *addrs,181int naddrs)182{183char mname[NS_MAXDNAME];184u_long save_pfcode;185rrset_ns nsrrs;186int n;187188DPRINTF(("START dname='%s' class=%s, zsize=%ld, naddrs=%d",189dname, p_class(class), (long)zsize, naddrs));190save_pfcode = statp->pfcode;191statp->pfcode |= RES_PRF_HEAD2 | RES_PRF_HEAD1 | RES_PRF_HEADX |192RES_PRF_QUES | RES_PRF_ANS |193RES_PRF_AUTH | RES_PRF_ADD;194INIT_LIST(nsrrs);195196DPRINTF(("get the soa, and see if it has enough glue"));197if ((n = get_soa(statp, dname, class, opts, zname, zsize,198mname, sizeof mname, &nsrrs)) < 0 ||199((opts & RES_EXHAUSTIVE) == 0 &&200(n = satisfy(statp, mname, &nsrrs, addrs, naddrs)) > 0))201goto done;202203DPRINTF(("get the ns rrset and see if it has enough glue"));204if ((n = get_ns(statp, zname, class, opts, &nsrrs)) < 0 ||205((opts & RES_EXHAUSTIVE) == 0 &&206(n = satisfy(statp, mname, &nsrrs, addrs, naddrs)) > 0))207goto done;208209DPRINTF(("get the missing glue and see if it's finally enough"));210if ((n = get_glue(statp, class, opts, &nsrrs)) >= 0)211n = satisfy(statp, mname, &nsrrs, addrs, naddrs);212213done:214DPRINTF(("FINISH n=%d (%s)", n, (n < 0) ? strerror(errno) : "OK"));215free_nsrrset(&nsrrs);216statp->pfcode = save_pfcode;217return (n);218}219220/* Private. */221222static int223satisfy(res_state statp, const char *mname, rrset_ns *nsrrsp,224union res_sockaddr_union *addrs, int naddrs)225{226rr_ns *nsrr;227int n, x;228229n = 0;230nsrr = find_ns(nsrrsp, mname);231if (nsrr != NULL) {232x = add_addrs(statp, nsrr, addrs, naddrs);233addrs += x;234naddrs -= x;235n += x;236}237for (nsrr = HEAD(*nsrrsp);238nsrr != NULL && naddrs > 0;239nsrr = NEXT(nsrr, link))240if (ns_samename(nsrr->name, mname) != 1) {241x = add_addrs(statp, nsrr, addrs, naddrs);242addrs += x;243naddrs -= x;244n += x;245}246DPRINTF(("satisfy(%s): %d", mname, n));247return (n);248}249250static int251add_addrs(res_state statp, rr_ns *nsrr,252union res_sockaddr_union *addrs, int naddrs)253{254rr_a *arr;255int n = 0;256257for (arr = HEAD(nsrr->addrs); arr != NULL; arr = NEXT(arr, link)) {258if (naddrs <= 0)259return (0);260*addrs++ = arr->addr;261naddrs--;262n++;263}264DPRINTF(("add_addrs: %d", n));265return (n);266}267268static int269get_soa(res_state statp, const char *dname, ns_class class, int opts,270char *zname, size_t zsize, char *mname, size_t msize,271rrset_ns *nsrrsp)272{273char tname[NS_MAXDNAME];274u_char *resp = NULL;275int n, i, ancount, nscount;276ns_sect sect;277ns_msg msg;278u_int rcode;279280/*281* Find closest enclosing SOA, even if it's for the root zone.282*/283284/* First canonicalize dname (exactly one unescaped trailing "."). */285if (ns_makecanon(dname, tname, sizeof tname) < 0)286goto cleanup;287dname = tname;288289resp = malloc(NS_MAXMSG);290if (resp == NULL)291goto cleanup;292293/* Now grovel the subdomains, hunting for an SOA answer or auth. */294for (;;) {295/* Leading or inter-label '.' are skipped here. */296while (*dname == '.')297dname++;298299/* Is there an SOA? */300n = do_query(statp, dname, class, ns_t_soa, resp, &msg);301if (n < 0) {302DPRINTF(("get_soa: do_query('%s', %s) failed (%d)",303dname, p_class(class), n));304goto cleanup;305}306if (n > 0) {307DPRINTF(("get_soa: CNAME or DNAME found"));308sect = ns_s_max, n = 0;309} else {310rcode = ns_msg_getflag(msg, ns_f_rcode);311ancount = ns_msg_count(msg, ns_s_an);312nscount = ns_msg_count(msg, ns_s_ns);313if (ancount > 0 && rcode == ns_r_noerror)314sect = ns_s_an, n = ancount;315else if (nscount > 0)316sect = ns_s_ns, n = nscount;317else318sect = ns_s_max, n = 0;319}320for (i = 0; i < n; i++) {321const char *t;322const u_char *rdata;323ns_rr rr;324325if (ns_parserr(&msg, sect, i, &rr) < 0) {326DPRINTF(("get_soa: ns_parserr(%s, %d) failed",327p_section(sect, ns_o_query), i));328goto cleanup;329}330if (ns_rr_type(rr) == ns_t_cname ||331ns_rr_type(rr) == ns_t_dname)332break;333if (ns_rr_type(rr) != ns_t_soa ||334ns_rr_class(rr) != class)335continue;336t = ns_rr_name(rr);337switch (sect) {338case ns_s_an:339if (ns_samedomain(dname, t) == 0) {340DPRINTF(341("get_soa: ns_samedomain('%s', '%s') == 0",342dname, t)343);344errno = EPROTOTYPE;345goto cleanup;346}347break;348case ns_s_ns:349if (ns_samename(dname, t) == 1 ||350ns_samedomain(dname, t) == 0) {351DPRINTF(352("get_soa: ns_samename() || !ns_samedomain('%s', '%s')",353dname, t)354);355errno = EPROTOTYPE;356goto cleanup;357}358break;359default:360abort();361}362if (strlen(t) + 1 > zsize) {363DPRINTF(("get_soa: zname(%lu) too small (%lu)",364(unsigned long)zsize,365(unsigned long)strlen(t) + 1));366errno = EMSGSIZE;367goto cleanup;368}369strcpy(zname, t);370rdata = ns_rr_rdata(rr);371if (ns_name_uncompress(resp, ns_msg_end(msg), rdata,372mname, msize) < 0) {373DPRINTF(("get_soa: ns_name_uncompress failed")374);375goto cleanup;376}377if (save_ns(statp, &msg, ns_s_ns,378zname, class, opts, nsrrsp) < 0) {379DPRINTF(("get_soa: save_ns failed"));380goto cleanup;381}382free(resp);383return (0);384}385386/* If we're out of labels, then not even "." has an SOA! */387if (*dname == '\0')388break;389390/* Find label-terminating "."; top of loop will skip it. */391while (*dname != '.') {392if (*dname == '\\')393if (*++dname == '\0') {394errno = EMSGSIZE;395goto cleanup;396}397dname++;398}399}400DPRINTF(("get_soa: out of labels"));401errno = EDESTADDRREQ;402cleanup:403if (resp != NULL)404free(resp);405return (-1);406}407408static int409get_ns(res_state statp, const char *zname, ns_class class, int opts,410rrset_ns *nsrrsp)411{412u_char *resp;413ns_msg msg;414int n;415416resp = malloc(NS_MAXMSG);417if (resp == NULL)418return (-1);419420/* Go and get the NS RRs for this zone. */421n = do_query(statp, zname, class, ns_t_ns, resp, &msg);422if (n != 0) {423DPRINTF(("get_ns: do_query('%s', %s) failed (%d)",424zname, p_class(class), n));425free(resp);426return (-1);427}428429/* Remember the NS RRs and associated A RRs that came back. */430if (save_ns(statp, &msg, ns_s_an, zname, class, opts, nsrrsp) < 0) {431DPRINTF(("get_ns save_ns('%s', %s) failed",432zname, p_class(class)));433free(resp);434return (-1);435}436437free(resp);438return (0);439}440441static int442get_glue(res_state statp, ns_class class, int opts, rrset_ns *nsrrsp) {443rr_ns *nsrr, *nsrr_n;444u_char *resp;445446resp = malloc(NS_MAXMSG);447if (resp == NULL)448return(-1);449450/* Go and get the A RRs for each empty NS RR on our list. */451for (nsrr = HEAD(*nsrrsp); nsrr != NULL; nsrr = nsrr_n) {452ns_msg msg;453int n;454455nsrr_n = NEXT(nsrr, link);456457if ((nsrr->flags & RR_NS_HAVE_V4) == 0) {458n = do_query(statp, nsrr->name, class, ns_t_a,459resp, &msg);460if (n < 0) {461DPRINTF(462("get_glue: do_query('%s', %s') failed",463nsrr->name, p_class(class)));464goto cleanup;465}466if (n > 0) {467DPRINTF((468"get_glue: do_query('%s', %s') CNAME or DNAME found",469nsrr->name, p_class(class)));470}471if (save_a(statp, &msg, ns_s_an, nsrr->name, class,472opts, nsrr) < 0) {473DPRINTF(("get_glue: save_r('%s', %s) failed",474nsrr->name, p_class(class)));475goto cleanup;476}477}478479if ((nsrr->flags & RR_NS_HAVE_V6) == 0) {480n = do_query(statp, nsrr->name, class, ns_t_aaaa,481resp, &msg);482if (n < 0) {483DPRINTF(484("get_glue: do_query('%s', %s') failed",485nsrr->name, p_class(class)));486goto cleanup;487}488if (n > 0) {489DPRINTF((490"get_glue: do_query('%s', %s') CNAME or DNAME found",491nsrr->name, p_class(class)));492}493if (save_a(statp, &msg, ns_s_an, nsrr->name, class,494opts, nsrr) < 0) {495DPRINTF(("get_glue: save_r('%s', %s) failed",496nsrr->name, p_class(class)));497goto cleanup;498}499}500501/* If it's still empty, it's just chaff. */502if (EMPTY(nsrr->addrs)) {503DPRINTF(("get_glue: removing empty '%s' NS",504nsrr->name));505free_nsrr(nsrrsp, nsrr);506}507}508free(resp);509return (0);510511cleanup:512free(resp);513return (-1);514}515516static int517save_ns(res_state statp, ns_msg *msg, ns_sect sect,518const char *owner, ns_class class, int opts,519rrset_ns *nsrrsp)520{521int i;522523for (i = 0; i < ns_msg_count(*msg, sect); i++) {524char tname[MAXDNAME];525const u_char *rdata;526rr_ns *nsrr;527ns_rr rr;528529if (ns_parserr(msg, sect, i, &rr) < 0) {530DPRINTF(("save_ns: ns_parserr(%s, %d) failed",531p_section(sect, ns_o_query), i));532return (-1);533}534if (ns_rr_type(rr) != ns_t_ns ||535ns_rr_class(rr) != class ||536ns_samename(ns_rr_name(rr), owner) != 1)537continue;538nsrr = find_ns(nsrrsp, ns_rr_name(rr));539if (nsrr == NULL) {540nsrr = malloc(sizeof *nsrr);541if (nsrr == NULL) {542DPRINTF(("save_ns: malloc failed"));543return (-1);544}545rdata = ns_rr_rdata(rr);546if (ns_name_uncompress(ns_msg_base(*msg),547ns_msg_end(*msg), rdata,548tname, sizeof tname) < 0) {549DPRINTF(("save_ns: ns_name_uncompress failed")550);551free(nsrr);552return (-1);553}554nsrr->name = strdup(tname);555if (nsrr->name == NULL) {556DPRINTF(("save_ns: strdup failed"));557free(nsrr);558return (-1);559}560INIT_LINK(nsrr, link);561INIT_LIST(nsrr->addrs);562nsrr->flags = 0;563APPEND(*nsrrsp, nsrr, link);564}565if (save_a(statp, msg, ns_s_ar,566nsrr->name, class, opts, nsrr) < 0) {567DPRINTF(("save_ns: save_r('%s', %s) failed",568nsrr->name, p_class(class)));569return (-1);570}571}572return (0);573}574575static int576save_a(res_state statp, ns_msg *msg, ns_sect sect,577const char *owner, ns_class class, int opts,578rr_ns *nsrr)579{580int i;581582for (i = 0; i < ns_msg_count(*msg, sect); i++) {583ns_rr rr;584rr_a *arr;585586if (ns_parserr(msg, sect, i, &rr) < 0) {587DPRINTF(("save_a: ns_parserr(%s, %d) failed",588p_section(sect, ns_o_query), i));589return (-1);590}591if ((ns_rr_type(rr) != ns_t_a &&592ns_rr_type(rr) != ns_t_aaaa) ||593ns_rr_class(rr) != class ||594ns_samename(ns_rr_name(rr), owner) != 1 ||595ns_rr_rdlen(rr) != NS_INADDRSZ)596continue;597if ((opts & RES_IPV6ONLY) != 0 && ns_rr_type(rr) != ns_t_aaaa)598continue;599if ((opts & RES_IPV4ONLY) != 0 && ns_rr_type(rr) != ns_t_a)600continue;601arr = malloc(sizeof *arr);602if (arr == NULL) {603DPRINTF(("save_a: malloc failed"));604return (-1);605}606INIT_LINK(arr, link);607memset(&arr->addr, 0, sizeof(arr->addr));608switch (ns_rr_type(rr)) {609case ns_t_a:610arr->addr.sin.sin_family = AF_INET;611#ifdef HAVE_SA_LEN612arr->addr.sin.sin_len = sizeof(arr->addr.sin);613#endif614memcpy(&arr->addr.sin.sin_addr, ns_rr_rdata(rr),615NS_INADDRSZ);616arr->addr.sin.sin_port = htons(NAMESERVER_PORT);617nsrr->flags |= RR_NS_HAVE_V4;618break;619case ns_t_aaaa:620arr->addr.sin6.sin6_family = AF_INET6;621#ifdef HAVE_SA_LEN622arr->addr.sin6.sin6_len = sizeof(arr->addr.sin6);623#endif624memcpy(&arr->addr.sin6.sin6_addr, ns_rr_rdata(rr), 16);625arr->addr.sin6.sin6_port = htons(NAMESERVER_PORT);626nsrr->flags |= RR_NS_HAVE_V6;627break;628default:629abort();630}631APPEND(nsrr->addrs, arr, link);632}633return (0);634}635636static void637free_nsrrset(rrset_ns *nsrrsp) {638rr_ns *nsrr;639640while ((nsrr = HEAD(*nsrrsp)) != NULL)641free_nsrr(nsrrsp, nsrr);642}643644static void645free_nsrr(rrset_ns *nsrrsp, rr_ns *nsrr) {646rr_a *arr;647char *tmp;648649while ((arr = HEAD(nsrr->addrs)) != NULL) {650UNLINK(nsrr->addrs, arr, link);651free(arr);652}653DE_CONST(nsrr->name, tmp);654free(tmp);655UNLINK(*nsrrsp, nsrr, link);656free(nsrr);657}658659static rr_ns *660find_ns(rrset_ns *nsrrsp, const char *dname) {661rr_ns *nsrr;662663for (nsrr = HEAD(*nsrrsp); nsrr != NULL; nsrr = NEXT(nsrr, link))664if (ns_samename(nsrr->name, dname) == 1)665return (nsrr);666return (NULL);667}668669static int670do_query(res_state statp, const char *dname, ns_class class, ns_type qtype,671u_char *resp, ns_msg *msg)672{673u_char req[NS_PACKETSZ];674int i, n;675676n = res_nmkquery(statp, ns_o_query, dname, class, qtype,677NULL, 0, NULL, req, NS_PACKETSZ);678if (n < 0) {679DPRINTF(("do_query: res_nmkquery failed"));680return (-1);681}682n = res_nsend(statp, req, n, resp, NS_MAXMSG);683if (n < 0) {684DPRINTF(("do_query: res_nsend failed"));685return (-1);686}687if (n == 0) {688DPRINTF(("do_query: res_nsend returned 0"));689errno = EMSGSIZE;690return (-1);691}692if (ns_initparse(resp, n, msg) < 0) {693DPRINTF(("do_query: ns_initparse failed"));694return (-1);695}696n = 0;697for (i = 0; i < ns_msg_count(*msg, ns_s_an); i++) {698ns_rr rr;699700if (ns_parserr(msg, ns_s_an, i, &rr) < 0) {701DPRINTF(("do_query: ns_parserr failed"));702return (-1);703}704n += (ns_rr_class(rr) == class &&705(ns_rr_type(rr) == ns_t_cname ||706ns_rr_type(rr) == ns_t_dname));707}708return (n);709}710711static void712res_dprintf(const char *fmt, ...) {713va_list ap;714715va_start(ap, fmt);716fputs(";; res_findzonecut: ", stderr);717vfprintf(stderr, fmt, ap);718fputc('\n', stderr);719va_end(ap);720}721722/*! \file */723724725