Date: Mon, 29 Jun 2026 15:52:07 +0000 From: Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav <des@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: 9fd229512332 - main - libc/resolv: Refactor the configuration parser Message-ID: <6a4294a7.3bfc6.7df80ba0@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=9fd22951233219b5e9985575c1b3aaf3532961fd commit 9fd22951233219b5e9985575c1b3aaf3532961fd Author: Dag-Erling Smørgrav <des@FreeBSD.org> AuthorDate: 2026-06-25 15:27:02 +0000 Commit: Dag-Erling Smørgrav <des@FreeBSD.org> CommitDate: 2026-06-29 13:49:18 +0000 libc/resolv: Refactor the configuration parser This was previously all a single loop in res_init(), apart from option parsing which we cleaned up in a previous commit. Break it out into separate functions for reading the configuration line by line, setting the default domain, setting the search list, and adding a nameserver to the nameserver list. Sprinkle bounds checks and code comments all around. The sortlist code, which has been disabled for the past 20 years, will be dealt with in a separate commit. MFC after: 1 week --- lib/libc/resolv/res_init.c | 669 +++++++++++++++++++++++++++------------------ 1 file changed, 401 insertions(+), 268 deletions(-) diff --git a/lib/libc/resolv/res_init.c b/lib/libc/resolv/res_init.c index 20f6824857b5..c61b247b5199 100644 --- a/lib/libc/resolv/res_init.c +++ b/lib/libc/resolv/res_init.c @@ -97,6 +97,10 @@ #include "res_private.h" +static void res_readconf(res_state, const char *, int); +static void res_setdefdname(res_state, const char *, const char *); +static void res_setdnsrch(res_state, const char *, const char *); +static int res_setnsaddr(res_state, int, const char *, const char *); static void res_setoptions(res_state, const char *, const char *); #ifdef RESOLVSORT @@ -107,6 +111,8 @@ static u_int32_t net_mask(struct in_addr); #define res_space(ch) \ isspace((unsigned char)(ch)) +#define res_comment(ch) \ + ((ch) == '#' || (ch) == ';') #define res_match(str, end, word) \ (strncmp((str), (word), (end) - (str)) == 0 && \ (word)[(end) - (str)] == '\0') @@ -136,7 +142,9 @@ res_ninit(res_state statp) { /*% This function has to be reachable by res_data.c but not publicly. */ int -__res_vinit(res_state statp, int preinit) { +__res_vinit(res_state statp, int preinit) +{ + char buf[NI_MAXHOST]; union res_sockaddr_union u[] = { { .sin = { .sin_family = AF_INET, @@ -157,17 +165,7 @@ __res_vinit(res_state statp, int preinit) { } }, #endif }; - FILE *fp; char *cp, **pp; - int n; - char buf[BUFSIZ]; - int nserv = 0; /*%< number of nameserver records read from file */ - int haveenv = 0; - int havesearch = 0; -#ifdef RESOLVSORT - int nsort = 0; - char *net; -#endif int dots; int maxns = MAXNS; @@ -219,263 +217,14 @@ __res_vinit(res_state statp, int preinit) { #endif res_setservers(statp, u, nitems(u)); + /* read the configuration file */ + res_readconf(statp, _PATH_RESCONF, maxns); + /* Allow user to override the local domain definition */ if ((cp = secure_getenv("LOCALDOMAIN")) != NULL) { - (void)strncpy(statp->defdname, cp, sizeof(statp->defdname) - 1); - statp->defdname[sizeof(statp->defdname) - 1] = '\0'; - haveenv++; - - /* - * Set search list to be blank-separated strings - * from rest of env value. Permits users of LOCALDOMAIN - * to still have a search list, and anyone to set the - * one that they want to use as an individual (even more - * important now that the rfc1535 stuff restricts searches) - */ - cp = statp->defdname; - pp = statp->dnsrch; - *pp++ = cp; - for (n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++) { - if (*cp == '\n') /*%< silly backwards compat */ - break; - else if (*cp == ' ' || *cp == '\t') { - *cp = 0; - n = 1; - } else if (n) { - *pp++ = cp; - n = 0; - havesearch = 1; - } - } - /* null terminate last domain if there are excess */ - while (*cp != '\0' && *cp != ' ' && *cp != '\t' && *cp != '\n') - cp++; - *cp = '\0'; - *pp++ = NULL; - } - -#define MATCH(line, name) \ - (!strncmp(line, name, sizeof(name) - 1) && \ - (line[sizeof(name) - 1] == ' ' || \ - line[sizeof(name) - 1] == '\t')) - - if ((fp = fopen(_PATH_RESCONF, "re")) != NULL) { - struct stat sb; - struct timespec now; - - if (statp->_u._ext.ext != NULL) { - if (_fstat(fileno(fp), &sb) == 0) { - statp->_u._ext.ext->conf_mtim = sb.st_mtim; - if (clock_gettime(CLOCK_MONOTONIC_FAST, &now) == 0) { - statp->_u._ext.ext->conf_stat = now.tv_sec; - } - } - } - - /* read the config file */ - while (fgets(buf, sizeof(buf), fp) != NULL) { - /* skip comments */ - if (*buf == ';' || *buf == '#') - continue; - /* read default domain name */ - if (MATCH(buf, "domain")) { - if (haveenv) /*%< skip if have from environ */ - continue; - cp = buf + sizeof("domain") - 1; - while (*cp == ' ' || *cp == '\t') - cp++; - if ((*cp == '\0') || (*cp == '\n')) - continue; - strncpy(statp->defdname, cp, sizeof(statp->defdname) - 1); - statp->defdname[sizeof(statp->defdname) - 1] = '\0'; - if ((cp = strpbrk(statp->defdname, " \t\n")) != NULL) - *cp = '\0'; - havesearch = 0; - continue; - } - /* set search list */ - if (MATCH(buf, "search")) { - if (haveenv) /*%< skip if have from environ */ - continue; - cp = buf + sizeof("search") - 1; - while (*cp == ' ' || *cp == '\t') - cp++; - if ((*cp == '\0') || (*cp == '\n')) - continue; - strncpy(statp->defdname, cp, sizeof(statp->defdname) - 1); - statp->defdname[sizeof(statp->defdname) - 1] = '\0'; - if ((cp = strchr(statp->defdname, '\n')) != NULL) - *cp = '\0'; - /* - * Set search list to be blank-separated strings - * on rest of line. - */ - cp = statp->defdname; - pp = statp->dnsrch; - *pp++ = cp; - for (n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++) { - if (*cp == ' ' || *cp == '\t') { - *cp = 0; - n = 1; - } else if (n) { - *pp++ = cp; - n = 0; - } - } - /* null terminate last domain if there are excess */ - while (*cp != '\0' && *cp != ' ' && *cp != '\t') - cp++; - *cp = '\0'; - *pp++ = NULL; - havesearch = 1; - continue; - } - /* read nameservers to query */ - if (MATCH(buf, "nameserver") && nserv < maxns) { - struct addrinfo hints, *ai; - char sbuf[NI_MAXSERV]; - const size_t minsiz = - sizeof(statp->_u._ext.ext->nsaddrs[0]); - - cp = buf + sizeof("nameserver") - 1; - while (*cp == ' ' || *cp == '\t') + while (res_space(*cp)) cp++; - cp[strcspn(cp, ";# \t\n")] = '\0'; - if ((*cp != '\0') && (*cp != '\n')) { - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; /*dummy*/ - hints.ai_flags = AI_NUMERICHOST; - sprintf(sbuf, "%u", NAMESERVER_PORT); - if (getaddrinfo(cp, sbuf, &hints, &ai) == 0) { - if (ai->ai_addrlen <= minsiz) { - if (statp->_u._ext.ext != NULL) { - memcpy(&statp->_u._ext.ext->nsaddrs[nserv], - ai->ai_addr, ai->ai_addrlen); - } - if (ai->ai_addrlen <= - sizeof(statp->nsaddr_list[nserv])) { - memcpy(&statp->nsaddr_list[nserv], - ai->ai_addr, ai->ai_addrlen); - } else - statp->nsaddr_list[nserv].sin_family = 0; - nserv++; - } - freeaddrinfo(ai); - } - } - continue; - } -#ifdef RESOLVSORT - if (MATCH(buf, "sortlist")) { - struct in_addr a; - struct in6_addr a6; - int m, i; - u_char *u; - struct __res_state_ext *ext = statp->_u._ext.ext; - - cp = buf + sizeof("sortlist") - 1; - while (nsort < MAXRESOLVSORT) { - while (*cp == ' ' || *cp == '\t') - cp++; - if (*cp == '\0' || *cp == '\n' || *cp == ';') - break; - net = cp; - while (*cp && !ISSORTMASK(*cp) && *cp != ';' && - isascii(*cp) && !isspace((unsigned char)*cp)) - cp++; - n = *cp; - *cp = 0; - if (inet_aton(net, &a)) { - statp->sort_list[nsort].addr = a; - if (ISSORTMASK(n)) { - *cp++ = n; - net = cp; - while (*cp && *cp != ';' && - isascii(*cp) && - !isspace((unsigned char)*cp)) - cp++; - n = *cp; - *cp = 0; - if (inet_aton(net, &a)) { - statp->sort_list[nsort].mask = a.s_addr; - } else { - statp->sort_list[nsort].mask = - net_mask(statp->sort_list[nsort].addr); - } - } else { - statp->sort_list[nsort].mask = - net_mask(statp->sort_list[nsort].addr); - } - ext->sort_list[nsort].af = AF_INET; - ext->sort_list[nsort].addr.ina = - statp->sort_list[nsort].addr; - ext->sort_list[nsort].mask.ina.s_addr = - statp->sort_list[nsort].mask; - nsort++; - } - else if (inet_pton(AF_INET6, net, &a6) == 1) { - - ext->sort_list[nsort].af = AF_INET6; - ext->sort_list[nsort].addr.in6a = a6; - u = (u_char *)&ext->sort_list[nsort].mask.in6a; - *cp++ = n; - net = cp; - while (*cp && *cp != ';' && - isascii(*cp) && !isspace(*cp)) - cp++; - m = n; - n = *cp; - *cp = 0; - switch (m) { - case '/': - m = atoi(net); - break; - case '&': - if (inet_pton(AF_INET6, net, u) == 1) { - m = -1; - break; - } - /*FALLTHROUGH*/ - default: - m = sizeof(struct in6_addr) * CHAR_BIT; - break; - } - if (m >= 0) { - for (i = 0; i < sizeof(struct in6_addr); i++) { - if (m <= 0) { - *u = 0; - } else { - m -= CHAR_BIT; - *u = (u_char)~0; - if (m < 0) - *u <<= -m; - } - u++; - } - } - statp->sort_list[nsort].addr.s_addr = - (u_int32_t)0xffffffff; - statp->sort_list[nsort].mask = - (u_int32_t)0xffffffff; - nsort++; - } - *cp = n; - } - continue; - } -#endif - if (MATCH(buf, "options")) { - res_setoptions(statp, buf + sizeof("options") - 1, "conf"); - continue; - } - } - if (nserv > 0) - statp->nscount = nserv; -#ifdef RESOLVSORT - statp->nsort = nsort; -#endif - (void) fclose(fp); + res_setdnsrch(statp, cp, NULL); } /* guess default domain if not set */ @@ -485,7 +234,7 @@ __res_vinit(res_state statp, int preinit) { strcpy(statp->defdname, cp + 1); /* find components of local domain that might be searched */ - if (havesearch == 0) { + if (statp->dnsrch[0] == NULL) { pp = statp->dnsrch; *pp++ = statp->defdname; *pp = NULL; @@ -521,6 +270,390 @@ __res_vinit(res_state statp, int preinit) { return (statp->res_h_errno); } +/*% + * Read the resolver configuration file. + */ +static void +res_readconf(res_state statp, const char *resconf, int maxns) +{ + struct stat sb; + struct timespec now; + FILE *fp; + char *line = NULL, *eol, *eow; + char *cp; + ssize_t len; + size_t sz = 0; + int nserv = 0; +#ifdef RESOLVSORT + int n, nsort = 0; + char *net; +#endif + + if ((fp = fopen(resconf, "re")) == NULL) + return; + if (statp->_u._ext.ext != NULL) { + if (_fstat(fileno(fp), &sb) == 0) { + statp->_u._ext.ext->conf_mtim = sb.st_mtim; + if (clock_gettime(CLOCK_MONOTONIC_FAST, &now) == 0) { + statp->_u._ext.ext->conf_stat = now.tv_sec; + } + } + } + + while ((len = getline(&line, &sz, fp)) != -1) { + /* search for comment or end of line */ + eol = line; + while (eol < line + len && !res_comment(*eol)) + eol++; + + /* now backtrack over whitespace */ + while (eol > line && res_space(eol[-1])) + eol--; + *eol = '\0'; + + /* skip blank line */ + if (eol == line) + continue; + + /* isolate the first word */ + eow = line; + while (eow < eol && !res_space(*eow)) + eow++; + + /* read default domain name */ + if (res_match(line, eow, "domain")) { + cp = eow; + while (cp < eol && res_space(*cp)) + cp++; + res_setdefdname(statp, cp, eol); + continue; + } + + /* set search list */ + if (res_match(line, eow, "search")) { + cp = eow; + while (cp < eol && res_space(*cp)) + cp++; + res_setdnsrch(statp, cp, eol); + continue; + } + + /* read nameservers to query */ + if (res_match(line, eow, "nameserver")) { + cp = eow; + while (cp < eol && res_space(*cp)) + cp++; + if (cp == eol) + continue; + if (nserv < maxns) { + if (res_setnsaddr(statp, nserv, cp, eol)) + nserv++; +#ifdef DEBUG + } else { + printf(";; nameserver overflow: %.*s\n", + (int)(eol - cp), cp); +#endif + } + continue; + } +#ifdef RESOLVSORT + if (res_match(line, eow, "sortlist")) { + struct in_addr a; + struct in6_addr a6; + int m, i; + u_char *u; + struct __res_state_ext *ext = statp->_u._ext.ext; + + cp = line + sizeof("sortlist") - 1; + while (nsort < MAXRESOLVSORT) { + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '\0' || *cp == '\n' || *cp == ';') + break; + net = cp; + while (*cp && !ISSORTMASK(*cp) && *cp != ';' && + isascii(*cp) && !isspace((unsigned char)*cp)) + cp++; + n = *cp; + *cp = 0; + if (inet_aton(net, &a)) { + statp->sort_list[nsort].addr = a; + if (ISSORTMASK(n)) { + *cp++ = n; + net = cp; + while (*cp && *cp != ';' && + isascii(*cp) && + !isspace((unsigned char)*cp)) + cp++; + n = *cp; + *cp = 0; + if (inet_aton(net, &a)) { + statp->sort_list[nsort].mask = a.s_addr; + } else { + statp->sort_list[nsort].mask = + net_mask(statp->sort_list[nsort].addr); + } + } else { + statp->sort_list[nsort].mask = + net_mask(statp->sort_list[nsort].addr); + } + ext->sort_list[nsort].af = AF_INET; + ext->sort_list[nsort].addr.ina = + statp->sort_list[nsort].addr; + ext->sort_list[nsort].mask.ina.s_addr = + statp->sort_list[nsort].mask; + nsort++; + } + else if (inet_pton(AF_INET6, net, &a6) == 1) { + + ext->sort_list[nsort].af = AF_INET6; + ext->sort_list[nsort].addr.in6a = a6; + u = (u_char *)&ext->sort_list[nsort].mask.in6a; + *cp++ = n; + net = cp; + while (*cp && *cp != ';' && + isascii(*cp) && !isspace(*cp)) + cp++; + m = n; + n = *cp; + *cp = 0; + switch (m) { + case '/': + m = atoi(net); + break; + case '&': + if (inet_pton(AF_INET6, net, u) == 1) { + m = -1; + break; + } + /*FALLTHROUGH*/ + default: + m = sizeof(struct in6_addr) * CHAR_BIT; + break; + } + if (m >= 0) { + for (i = 0; i < sizeof(struct in6_addr); i++) { + if (m <= 0) { + *u = 0; + } else { + m -= CHAR_BIT; + *u = (u_char)~0; + if (m < 0) + *u <<= -m; + } + u++; + } + } + statp->sort_list[nsort].addr.s_addr = + (u_int32_t)0xffffffff; + statp->sort_list[nsort].mask = + (u_int32_t)0xffffffff; + nsort++; + } + *cp = n; + } + continue; + } +#endif /* RESOLVSORT */ + /* read resolver options */ + if (res_match(line, eow, "options")) { + cp = eow; + while (res_space(*cp)) + cp++; + res_setoptions(statp, cp, "conf"); + continue; + } + } + free(line); + if (nserv > 0) + statp->nscount = nserv; +#ifdef RESOLVSORT + statp->nsort = nsort; +#endif + (void)fclose(fp); +} + +/*% + * Set the default domain to the first word of the string. Assumes no + * leading space. + */ +static void +res_setdefdname(res_state statp, const char *str, const char *end) +{ + const char *sp; + + /* find end of string */ + if (end == NULL) { + end = str; + while (*end != '\0') + end++; + } + + /* find the first space */ + sp = str; + while (sp < end && !res_space(*sp)) + sp++; + + /* check length of first word */ + if (sp == str || sp - str >= sizeof(statp->defdname)) { + statp->defdname[0] = '\0'; + statp->dnsrch[0] = NULL; + return; + } + + /* copy the first word into defdname */ + memcpy(statp->defdname, str, sp - str); + statp->defdname[sp - str] = '\0'; + statp->dnsrch[0] = NULL; +#ifdef DEBUG + printf(";; domain %s\n", statp->defdname); +#endif +} + +/*% + * Set the default domain to the first word of the string and split the + * string up into the search path. Assumes no leading space. + */ +static void +res_setdnsrch(res_state statp, const char *str, const char *end) +{ + char *sp, *wp, *np; + int n; + + /* find end of string */ + if (end == NULL) { + end = str; + while (*end != '\0') + end++; + } + + /* trim trailing space */ + while (end > str && res_space(end[-1])) + end--; + + /* check length of string */ + if (end == str || end - str >= sizeof(statp->defdname)) { + statp->defdname[0] = '\0'; + statp->dnsrch[0] = NULL; + return; + } + + /* copy entire string into defdname */ + memcpy(statp->defdname, str, end - str); + statp->defdname[end - str] = '\0'; + + /* split string into words */ + for (wp = statp->defdname, n = 0; + *wp != '\0' && n < MAXDNSRCH; + wp = np) { + /* find the end of the word */ + sp = wp; + while (*sp != '\0' && !res_space(*sp)) + sp++; + + /* find the start of the next word */ + np = sp; + while (*np != '\0' && res_space(*np)) + np++; + + /* assign to the next slot */ + if (sp > wp) { + statp->dnsrch[n] = wp; + *sp = '\0'; +#ifdef DEBUG + if (n == 0) + printf(";; domain %s\n;; search", statp->defdname); + printf(" %s", statp->dnsrch[n]); +#endif + n++; + } + } +#ifdef DEBUG + printf("\n"); +#endif + + /* terminate the list */ + statp->dnsrch[n] = NULL; +} + +/*% + * Resolve the string and insert the result in the specified slot in the + * name server list. Assumes no leading or trailing space. + */ +static int +res_setnsaddr(res_state statp, int i, const char *str, const char *end) +{ + char addr[48], port[8]; + _Static_assert(sizeof(addr) > INET6_ADDRSTRLEN, + "address buffer too small for AF_INET6"); + _Static_assert(sizeof(addr) > INET_ADDRSTRLEN, + "address buffer too small for AF_INET"); + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_flags = AI_NUMERICHOST, + }; + struct addrinfo *ai; + int ret = 0; + + /* find end of string */ + if (end == NULL) { + end = str; + while (*end != '\0') + end++; + } + + /* prepare port number */ + snprintf(port, sizeof(port), "%u", NAMESERVER_PORT); + + /* check length */ + if (end - str >= sizeof(addr)) { +#ifdef DEBUG + printf(";; nameserver too long: %.*s\n", + (int)(end - str), str); +#endif + return (0); + } + + /* resolve the address */ + memcpy(addr, str, end - str); + addr[end - str] = '\0'; + if (getaddrinfo(addr, port, &hints, &ai) != 0) { +#ifdef DEBUG + printf(";; nameserver invalid: %s\n", addr); +#endif + return (0); + } + + /* copy the sockaddr */ + if (ai->ai_addrlen <= sizeof(statp->_u._ext.ext->nsaddrs[0])) { + if (statp->_u._ext.ext != NULL) { + memcpy(&statp->_u._ext.ext->nsaddrs[i], + ai->ai_addr, ai->ai_addrlen); + ret = 1; + } + if (ai->ai_addrlen <= sizeof(statp->nsaddr_list[i])) { + memcpy(&statp->nsaddr_list[i], + ai->ai_addr, ai->ai_addrlen); + ret = 1; + } else { + statp->nsaddr_list[i].sin_family = 0; + } +#ifdef DEBUG + printf(";; nameserver %s\n", addr); +#endif + } else { +#ifdef DEBUG + printf(";; sockaddr too big: %s\n", addr); +#endif + } + freeaddrinfo(ai); + return (ret); +} + +/*% + * Split the string up into words and interpret them as resolver options. + */ static void res_setoptions(res_state statp, const char *options, const char *source) { @@ -737,7 +870,8 @@ res_get_nibblesuffix2(res_state statp) { #endif void -res_setservers(res_state statp, const union res_sockaddr_union *set, int cnt) { +res_setservers(res_state statp, const union res_sockaddr_union *set, int cnt) +{ int i, nserv; size_t size; @@ -784,7 +918,6 @@ res_setservers(res_state statp, const union res_sockaddr_union *set, int cnt) { set++; } statp->nscount = nserv; - } inthome | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a4294a7.3bfc6.7df80ba0>
