1
0
mirror of https://sourceware.org/git/glibc.git synced 2025-07-28 00:21:52 +03:00

resolv: Lift domain search list limits [BZ #19569] [BZ #21475]

This change uses the extended resolver state in struct resolv_conf to
store the search list.  If applications have not patched the _res
object directly, this extended search list will be used by the stub
resolver during name resolution.
This commit is contained in:
Florian Weimer
2017-07-01 00:53:05 +02:00
parent f30a54b21b
commit 3f853f22c8
8 changed files with 382 additions and 81 deletions

View File

@ -1,3 +1,29 @@
2017-06-30 Florian Weimer <fweimer@redhat.com>
[BZ #19569]
[BZ #21475]
Support an arbitrary number of search domains.
* resolv/resolv_context.h (__resolv_context_search_list): New.
* resolv/resolv_conf.h (struct resolv_conf): Add search_list,
search_list_size members.
* resolv/resolv_conf.c (resolv_conf_matches): Compare search list.
(__resolv_conf_allocate): Allocate and and copy search list.
(update_from_conf): Copy the search list.
* resolv/res_init.c (struct search_list): Define using dynarray.
(struct resolv_conf_parser): Define.
(resolv_conf_parser_init, resolv_conf_parser_free)
(domain_from_hostname): New functions.
(res_vinit_1): Add struct resolv_conf_parser * parameter. Use
struct search_list to collect search list entries. Call
domain_from_hostname to obtain the fallback domain name.
(__res_vinit): Create and destroy parser object. Pass search list
to __resolv_conf_allocate.
* resolv/res_query.c (__res_context_search): Use
__resolv_context_search_list to obtain search list entries.
* resolv/tst-resolv-res_init-skeleton.c (print_resp): Print data
from extended resolver context.
(test_cases): Update.
2017-06-30 Florian Weimer <fweimer@redhat.com> 2017-06-30 Florian Weimer <fweimer@redhat.com>
Add extended resolver state/configuration (struct resolv_conf). Add extended resolver state/configuration (struct resolv_conf).

8
NEWS
View File

@ -237,6 +237,14 @@ Version 2.26
* The _res_opcodes variable has been removed from libresolv. It had been * The _res_opcodes variable has been removed from libresolv. It had been
exported by accident. exported by accident.
* The glibc DNS stub resolver now supports an arbitary number of search
domains (configured using the “search” directive in /etc/resolv.conf).
Most applications will automatically benefit from this change, but for
backwards compatibility reasons, applications which directly modify _res
objects (which contain the resolver state, including the search list
array, which is limited to six entries) will only use the first six search
domains, as before.
Security related changes: Security related changes:
* The DNS stub resolver limits the advertised UDP buffer size to 1200 bytes, * The DNS stub resolver limits the advertised UDP buffer size to 1200 bytes,

View File

@ -124,18 +124,75 @@ is_sort_mask (char ch)
return ch == '/' || ch == '&'; return ch == '/' || ch == '&';
} }
/* Array of strings for the search array. The backing store is
managed separately. */
#define DYNARRAY_STRUCT search_list
#define DYNARRAY_ELEMENT const char *
#define DYNARRAY_INITIAL_SIZE 4
#define DYNARRAY_PREFIX search_list_
#include <malloc/dynarray-skeleton.c>
/* resolv.conf parser state and results. */
struct resolv_conf_parser
{
char *buffer; /* Temporary buffer for reading lines. */
char *search_list_store; /* Backing storage for search list entries. */
struct search_list search_list; /* Points into search_list_store. */
};
static void
resolv_conf_parser_init (struct resolv_conf_parser *parser)
{
parser->buffer = NULL;
parser->search_list_store = NULL;
search_list_init (&parser->search_list);
}
static void
resolv_conf_parser_free (struct resolv_conf_parser *parser)
{
free (parser->buffer);
free (parser->search_list_store);
search_list_free (&parser->search_list);
}
/* Try to obtain the domain name from the host name and store it in
*RESULT. Return false on memory allocation failure. If the domain
name cannot be determined for any other reason, write NULL to
*RESULT and return true. */
static bool
domain_from_hostname (char **result)
{
char buf[256];
/* gethostbyname may not terminate the buffer. */
buf[sizeof (buf) - 1] = '\0';
if (__gethostname (buf, sizeof (buf) - 1) == 0)
{
char *dot = strchr (buf, '.');
if (dot != NULL)
{
*result = __strdup (dot + 1);
if (*result == NULL)
return false;
return true;
}
}
*result = NULL;
return true;
}
/* Internal helper function for __res_vinit, to aid with resource /* Internal helper function for __res_vinit, to aid with resource
deallocation and error handling. Return true on success, false on deallocation and error handling. Return true on success, false on
failure. */ failure. */
static bool static bool
res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer) res_vinit_1 (res_state statp, bool preinit, FILE *fp,
struct resolv_conf_parser *parser)
{ {
char *cp, **pp; char *cp;
size_t buffer_size = 0; size_t buffer_size = 0;
int nserv = 0; /* Number of nameservers read from file. */ int nserv = 0; /* Number of nameservers read from file. */
bool have_serv6 = false; bool have_serv6 = false;
bool haveenv = false; bool haveenv = false;
bool havesearch = false;
int nsort = 0; int nsort = 0;
char *net; char *net;
@ -162,39 +219,40 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
/* Allow user to override the local domain definition. */ /* Allow user to override the local domain definition. */
if ((cp = getenv ("LOCALDOMAIN")) != NULL) if ((cp = getenv ("LOCALDOMAIN")) != NULL)
{ {
strncpy (statp->defdname, cp, sizeof (statp->defdname) - 1); /* The code below splits the string in place. */
statp->defdname[sizeof (statp->defdname) - 1] = '\0'; cp = __strdup (cp);
if (cp == NULL)
return false;
free (parser->search_list_store);
parser->search_list_store = cp;
haveenv = true; haveenv = true;
/* The string will be truncated as needed below. */
search_list_add (&parser->search_list, cp);
/* Set search list to be blank-separated strings from rest of /* Set search list to be blank-separated strings from rest of
env value. Permits users of LOCALDOMAIN to still have a env value. Permits users of LOCALDOMAIN to still have a
search list, and anyone to set the one that they want to use search list, and anyone to set the one that they want to use
as an individual (even more important now that the rfc1535 as an individual (even more important now that the rfc1535
stuff restricts searches). */ stuff restricts searches). */
cp = statp->defdname; for (bool in_name = true; *cp != '\0'; cp++)
pp = statp->dnsrch;
*pp++ = cp;
for (int n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++)
{ {
if (*cp == '\n') if (*cp == '\n')
break; {
*cp = '\0';
break;
}
else if (*cp == ' ' || *cp == '\t') else if (*cp == ' ' || *cp == '\t')
{ {
*cp = 0; *cp = '\0';
n = 1; in_name = false;
} }
else if (n > 0) else if (!in_name)
{ {
*pp++ = cp; search_list_add (&parser->search_list, cp);
n = 0; in_name = true;
havesearch = true;
} }
} }
/* Null terminate last domain if there are excess. */
while (*cp != '\0' && *cp != ' ' && *cp != '\t' && *cp != '\n')
cp++;
*cp = '\0';
*pp++ = 0;
} }
#define MATCH(line, name) \ #define MATCH(line, name) \
@ -210,7 +268,7 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
while (true) while (true)
{ {
{ {
ssize_t ret = __getline (buffer, &buffer_size, fp); ssize_t ret = __getline (&parser->buffer, &buffer_size, fp);
if (ret <= 0) if (ret <= 0)
{ {
if (_IO_ferror_unlocked (fp)) if (_IO_ferror_unlocked (fp))
@ -221,73 +279,82 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
} }
/* Skip comments. */ /* Skip comments. */
if (**buffer == ';' || **buffer == '#') if (*parser->buffer == ';' || *parser->buffer == '#')
continue; continue;
/* Read default domain name. */ /* Read default domain name. */
if (MATCH (*buffer, "domain")) if (MATCH (parser->buffer, "domain"))
{ {
if (haveenv) if (haveenv)
/* LOCALDOMAIN overrides the configuration file. */ /* LOCALDOMAIN overrides the configuration file. */
continue; continue;
cp = *buffer + sizeof ("domain") - 1; cp = parser->buffer + sizeof ("domain") - 1;
while (*cp == ' ' || *cp == '\t') while (*cp == ' ' || *cp == '\t')
cp++; cp++;
if ((*cp == '\0') || (*cp == '\n')) if ((*cp == '\0') || (*cp == '\n'))
continue; continue;
strncpy (statp->defdname, cp, sizeof (statp->defdname) - 1);
statp->defdname[sizeof (statp->defdname) - 1] = '\0'; cp = __strdup (cp);
if ((cp = strpbrk (statp->defdname, " \t\n")) != NULL) if (cp == NULL)
return false;
free (parser->search_list_store);
parser->search_list_store = cp;
search_list_clear (&parser->search_list);
search_list_add (&parser->search_list, cp);
/* Replace trailing whitespace. */
if ((cp = strpbrk (cp, " \t\n")) != NULL)
*cp = '\0'; *cp = '\0';
havesearch = false;
continue; continue;
} }
/* Set search list. */ /* Set search list. */
if (MATCH (*buffer, "search")) if (MATCH (parser->buffer, "search"))
{ {
if (haveenv) if (haveenv)
/* LOCALDOMAIN overrides the configuration file. */ /* LOCALDOMAIN overrides the configuration file. */
continue; continue;
cp = *buffer + sizeof ("search") - 1; cp = parser->buffer + sizeof ("search") - 1;
while (*cp == ' ' || *cp == '\t') while (*cp == ' ' || *cp == '\t')
cp++; cp++;
if ((*cp == '\0') || (*cp == '\n')) if ((*cp == '\0') || (*cp == '\n'))
continue; continue;
strncpy (statp->defdname, cp, sizeof (statp->defdname) - 1);
statp->defdname[sizeof (statp->defdname) - 1] = '\0'; {
if ((cp = strchr (statp->defdname, '\n')) != NULL) char *p = strchr (cp, '\n');
*cp = '\0'; if (p != NULL)
*p = '\0';
}
cp = __strdup (cp);
if (cp == NULL)
return false;
free (parser->search_list_store);
parser->search_list_store = cp;
/* The string is truncated below. */
search_list_clear (&parser->search_list);
search_list_add (&parser->search_list, cp);
/* Set search list to be blank-separated strings on rest /* Set search list to be blank-separated strings on rest
of line. */ of line. */
cp = statp->defdname; for (bool in_name = true; *cp != '\0'; cp++)
pp = statp->dnsrch;
*pp++ = cp;
for (int n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++)
{ {
if (*cp == ' ' || *cp == '\t') if (*cp == ' ' || *cp == '\t')
{ {
*cp = 0; *cp = '\0';
n = 1; in_name = false;
} }
else if (n) else if (!in_name)
{ {
*pp++ = cp; search_list_add (&parser->search_list, cp);
n = 0; in_name = true;
} }
} }
/* Null terminate last domain if there are excess. */
while (*cp != '\0' && *cp != ' ' && *cp != '\t')
cp++;
*cp = '\0';
*pp++ = 0;
havesearch = true;
continue; continue;
} }
/* Read nameservers to query. */ /* Read nameservers to query. */
if (MATCH (*buffer, "nameserver") && nserv < MAXNS) if (MATCH (parser->buffer, "nameserver") && nserv < MAXNS)
{ {
struct in_addr a; struct in_addr a;
cp = *buffer + sizeof ("nameserver") - 1; cp = parser->buffer + sizeof ("nameserver") - 1;
while (*cp == ' ' || *cp == '\t') while (*cp == ' ' || *cp == '\t')
cp++; cp++;
if ((*cp != '\0') && (*cp != '\n') && __inet_aton (cp, &a)) if ((*cp != '\0') && (*cp != '\n') && __inet_aton (cp, &a))
@ -335,11 +402,11 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
} }
continue; continue;
} }
if (MATCH (*buffer, "sortlist")) if (MATCH (parser->buffer, "sortlist"))
{ {
struct in_addr a; struct in_addr a;
cp = *buffer + sizeof ("sortlist") - 1; cp = parser->buffer + sizeof ("sortlist") - 1;
while (nsort < MAXRESOLVSORT) while (nsort < MAXRESOLVSORT)
{ {
while (*cp == ' ' || *cp == '\t') while (*cp == ' ' || *cp == '\t')
@ -379,9 +446,9 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
} }
continue; continue;
} }
if (MATCH (*buffer, "options")) if (MATCH (parser->buffer, "options"))
{ {
res_setoptions (statp, *buffer + sizeof ("options") - 1); res_setoptions (statp, parser->buffer + sizeof ("options") - 1);
continue; continue;
} }
} }
@ -399,25 +466,29 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
statp->nsaddr.sin_port = htons (NAMESERVER_PORT); statp->nsaddr.sin_port = htons (NAMESERVER_PORT);
statp->nscount = 1; statp->nscount = 1;
} }
if (statp->defdname[0] == 0)
{
char buf[sizeof (statp->defdname)];
if (__gethostname (buf, sizeof (statp->defdname) - 1) == 0
&& (cp = strchr (buf, '.')) != NULL)
strcpy (statp->defdname, cp + 1);
}
/* Find components of local domain that might be searched. */ if (search_list_size (&parser->search_list) == 0)
if (!havesearch)
{ {
pp = statp->dnsrch; char *domain;
*pp++ = statp->defdname; if (!domain_from_hostname (&domain))
*pp = NULL; return false;
if (domain != NULL)
{
free (parser->search_list_store);
parser->search_list_store = domain;
search_list_add (&parser->search_list, domain);
}
} }
if ((cp = getenv ("RES_OPTIONS")) != NULL) if ((cp = getenv ("RES_OPTIONS")) != NULL)
res_setoptions (statp, cp); res_setoptions (statp, cp);
if (search_list_has_failed (&parser->search_list))
{
__set_errno (ENOMEM);
return false;
}
statp->options |= RES_INIT; statp->options |= RES_INIT;
return true; return true;
} }
@ -453,13 +524,17 @@ __res_vinit (res_state statp, int preinit)
return -1; return -1;
} }
char *buffer = NULL; struct resolv_conf_parser parser;
bool ok = res_vinit_1 (statp, preinit, fp, &buffer); resolv_conf_parser_init (&parser);
free (buffer); bool ok = res_vinit_1 (statp, preinit, fp, &parser);
if (ok) if (ok)
{ {
struct resolv_conf init = { 0 }; /* No data yet. */ struct resolv_conf init =
{
.search_list = search_list_begin (&parser.search_list),
.search_list_size = search_list_size (&parser.search_list),
};
struct resolv_conf *conf = __resolv_conf_allocate (&init); struct resolv_conf *conf = __resolv_conf_allocate (&init);
if (conf == NULL) if (conf == NULL)
ok = false; ok = false;
@ -469,6 +544,7 @@ __res_vinit (res_state statp, int preinit)
__resolv_conf_put (conf); __resolv_conf_put (conf);
} }
} }
resolv_conf_parser_free (&parser);
if (!ok) if (!ok)
{ {

View File

@ -326,7 +326,7 @@ __res_context_search (struct resolv_context *ctx,
int *nanswerp2, int *resplen2, int *answerp2_malloced) int *nanswerp2, int *resplen2, int *answerp2_malloced)
{ {
struct __res_state *statp = ctx->resp; struct __res_state *statp = ctx->resp;
const char *cp, * const *domain; const char *cp;
HEADER *hp = (HEADER *) answer; HEADER *hp = (HEADER *) answer;
char tmp[NS_MAXDNAME]; char tmp[NS_MAXDNAME];
u_int dots; u_int dots;
@ -392,10 +392,11 @@ __res_context_search (struct resolv_context *ctx,
(dots && !trailing_dot && (statp->options & RES_DNSRCH) != 0)) { (dots && !trailing_dot && (statp->options & RES_DNSRCH) != 0)) {
int done = 0; int done = 0;
for (domain = (const char * const *)statp->dnsrch; for (size_t domain_index = 0; !done; ++domain_index) {
*domain && !done; const char *dname = __resolv_context_search_list
domain++) { (ctx, domain_index);
const char *dname = domain[0]; if (dname == NULL)
break;
searched = 1; searched = 1;
/* __res_context_querydoman concatenates name /* __res_context_querydoman concatenates name

View File

@ -133,6 +133,34 @@ static bool
resolv_conf_matches (const struct __res_state *resp, resolv_conf_matches (const struct __res_state *resp,
const struct resolv_conf *conf) const struct resolv_conf *conf)
{ {
/* Check that the search list in *RESP has not been modified by the
application. */
{
if (!(resp->dnsrch[0] == resp->defdname
&& resp->dnsrch[MAXDNSRCH] == NULL))
return false;
size_t search_list_size = 0;
for (size_t i = 0; i < conf->search_list_size; ++i)
{
if (resp->dnsrch[i] != NULL)
{
search_list_size += strlen (resp->dnsrch[i]) + 1;
if (strcmp (resp->dnsrch[i], conf->search_list[i]) != 0)
return false;
}
else
{
/* resp->dnsrch is truncated if the number of elements
exceeds MAXDNSRCH, or if the combined storage space for
the search list exceeds what can be stored in
resp->defdname. */
if (i == MAXDNSRCH || search_list_size > sizeof (resp->dnsrch))
break;
/* Otherwise, a mismatch indicates a match failure. */
return false;
}
}
}
return true; return true;
} }
@ -162,10 +190,17 @@ __resolv_conf_put (struct resolv_conf *conf)
struct resolv_conf * struct resolv_conf *
__resolv_conf_allocate (const struct resolv_conf *init) __resolv_conf_allocate (const struct resolv_conf *init)
{ {
/* Space needed by the strings. */
size_t string_space = 0;
for (size_t i = 0; i < init->search_list_size; ++i)
string_space += strlen (init->search_list[i]) + 1;
/* Allocate the buffer. */ /* Allocate the buffer. */
void *ptr; void *ptr;
struct alloc_buffer buffer = alloc_buffer_allocate struct alloc_buffer buffer = alloc_buffer_allocate
(sizeof (struct resolv_conf), (sizeof (struct resolv_conf)
+ init->search_list_size * sizeof (init->search_list[0])
+ string_space,
&ptr); &ptr);
struct resolv_conf *conf struct resolv_conf *conf
= alloc_buffer_alloc (&buffer, struct resolv_conf); = alloc_buffer_alloc (&buffer, struct resolv_conf);
@ -178,6 +213,16 @@ __resolv_conf_allocate (const struct resolv_conf *init)
conf->__refcount = 1; conf->__refcount = 1;
conf->initstamp = __res_initstamp; conf->initstamp = __res_initstamp;
/* Allocate and fill the search list array. */
{
conf->search_list_size = init->search_list_size;
const char **array = alloc_buffer_alloc_array
(&buffer, const char *, init->search_list_size);
conf->search_list = array;
for (size_t i = 0; i < init->search_list_size; ++i)
array[i] = alloc_buffer_copy_string (&buffer, init->search_list[i]);
}
assert (!alloc_buffer_has_failed (&buffer)); assert (!alloc_buffer_has_failed (&buffer));
return conf; return conf;
} }
@ -186,6 +231,24 @@ __resolv_conf_allocate (const struct resolv_conf *init)
static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
update_from_conf (struct __res_state *resp, const struct resolv_conf *conf) update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
{ {
/* Fill in the prefix of the search list. It is truncated either at
MAXDNSRCH, or if reps->defdname has insufficient space. */
{
struct alloc_buffer buffer
= alloc_buffer_create (resp->defdname, sizeof (resp->defdname));
size_t size = conf->search_list_size;
size_t i;
for (i = 0; i < size && i < MAXDNSRCH; ++i)
{
resp->dnsrch[i] = alloc_buffer_copy_string
(&buffer, conf->search_list[i]);
if (resp->dnsrch[i] == NULL)
/* No more space in resp->defdname. Truncate. */
break;
}
resp->dnsrch[i] = NULL;
}
/* The overlapping parts of both configurations should agree after /* The overlapping parts of both configurations should agree after
initialization. */ initialization. */
assert (resolv_conf_matches (resp, conf)); assert (resolv_conf_matches (resp, conf));

View File

@ -35,6 +35,10 @@ struct resolv_conf
/* Reference counter. The object is deallocated once it reaches /* Reference counter. The object is deallocated once it reaches
zero. For internal use within resolv_conf only. */ zero. For internal use within resolv_conf only. */
size_t __refcount; size_t __refcount;
/* The domain names forming the search list. */
const char *const *search_list;
size_t search_list_size;
}; };
/* The functions below are for use by the res_init resolv.conf parser /* The functions below are for use by the res_init resolv.conf parser

View File

@ -93,6 +93,25 @@ struct resolv_context *__resolv_context_get_override (struct __res_state *)
__attribute__ ((nonnull (1), warn_unused_result)); __attribute__ ((nonnull (1), warn_unused_result));
libc_hidden_proto (__resolv_context_get_override) libc_hidden_proto (__resolv_context_get_override)
/* Return the search path entry at INDEX, or NULL if there are fewer
than INDEX entries. */
static __attribute__ ((nonnull (1), unused)) const char *
__resolv_context_search_list (const struct resolv_context *ctx, size_t index)
{
if (ctx->conf != NULL)
{
if (index < ctx->conf->search_list_size)
return ctx->conf->search_list[index];
else
return NULL;
}
/* Fallback. ctx->resp->dnsrch is a NULL-terminated array. */
for (size_t i = 0; ctx->resp->dnsrch[i] != NULL && i < MAXDNSRCH; ++i)
if (i == index)
return ctx->resp->dnsrch[i];
return NULL;
}
/* Called during thread shutdown to free the associated resolver /* Called during thread shutdown to free the associated resolver
context (mostly in response to cancellation, otherwise the context (mostly in response to cancellation, otherwise the
__resolv_context_get/__resolv_context_put pairing will already have __resolv_context_get/__resolv_context_put pairing will already have

View File

@ -25,6 +25,7 @@
#include <gnu/lib-names.h> #include <gnu/lib-names.h>
#include <netdb.h> #include <netdb.h>
#include <resolv/resolv-internal.h> /* For DEPRECATED_RES_USE_INET6. */ #include <resolv/resolv-internal.h> /* For DEPRECATED_RES_USE_INET6. */
#include <resolv/resolv_context.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <support/capture_subprocess.h> #include <support/capture_subprocess.h>
@ -116,6 +117,11 @@ print_option_flag (FILE *fp, int *options, int flag, const char *name)
static void static void
print_resp (FILE *fp, res_state resp) print_resp (FILE *fp, res_state resp)
{ {
struct resolv_context *ctx = __resolv_context_get_override (resp);
TEST_VERIFY_EXIT (ctx != NULL);
if (ctx->conf == NULL)
fprintf (fp, "; extended resolver state missing\n");
/* The options directive. */ /* The options directive. */
{ {
/* RES_INIT is used internally for tracking initialization. */ /* RES_INIT is used internally for tracking initialization. */
@ -165,6 +171,19 @@ print_resp (FILE *fp, res_state resp)
else if (resp->defdname[0] != '\0') else if (resp->defdname[0] != '\0')
fprintf (fp, "domain %s\n", resp->defdname); fprintf (fp, "domain %s\n", resp->defdname);
/* The extended search path. */
{
size_t i = 0;
while (true)
{
const char *name = __resolv_context_search_list (ctx, i);
if (name == NULL)
break;
fprintf (fp, "; search[%zu]: %s\n", i, name);
++i;
}
}
/* The sortlist directive. */ /* The sortlist directive. */
if (resp->nsort > 0) if (resp->nsort > 0)
{ {
@ -224,6 +243,8 @@ print_resp (FILE *fp, res_state resp)
} }
TEST_VERIFY (!ferror (fp)); TEST_VERIFY (!ferror (fp));
__resolv_context_put (ctx);
} }
/* Parameters of one test case. */ /* Parameters of one test case. */
@ -368,11 +389,13 @@ struct test_case test_cases[] =
{.name = "empty file", {.name = "empty file",
.conf = "", .conf = "",
.expected = "search example.com\n" .expected = "search example.com\n"
"; search[0]: example.com\n"
"nameserver 127.0.0.1\n" "nameserver 127.0.0.1\n"
}, },
{.name = "empty file with LOCALDOMAIN", {.name = "empty file with LOCALDOMAIN",
.conf = "", .conf = "",
.expected = "search example.net\n" .expected = "search example.net\n"
"; search[0]: example.net\n"
"nameserver 127.0.0.1\n", "nameserver 127.0.0.1\n",
.localdomain = "example.net", .localdomain = "example.net",
}, },
@ -380,6 +403,7 @@ struct test_case test_cases[] =
.conf = "", .conf = "",
.expected = "options attempts:5 edns0\n" .expected = "options attempts:5 edns0\n"
"search example.com\n" "search example.com\n"
"; search[0]: example.com\n"
"nameserver 127.0.0.1\n", "nameserver 127.0.0.1\n",
.res_options = "edns0 attempts:5", .res_options = "edns0 attempts:5",
}, },
@ -387,6 +411,7 @@ struct test_case test_cases[] =
.conf = "", .conf = "",
.expected = "options attempts:5 edns0\n" .expected = "options attempts:5 edns0\n"
"search example.org\n" "search example.org\n"
"; search[0]: example.org\n"
"nameserver 127.0.0.1\n", "nameserver 127.0.0.1\n",
.localdomain = "example.org", .localdomain = "example.org",
.res_options = "edns0 attempts:5", .res_options = "edns0 attempts:5",
@ -396,6 +421,8 @@ struct test_case test_cases[] =
"search corp.example.com example.com\n" "search corp.example.com example.com\n"
"nameserver 192.0.2.1\n", "nameserver 192.0.2.1\n",
.expected = "search corp.example.com example.com\n" .expected = "search corp.example.com example.com\n"
"; search[0]: corp.example.com\n"
"; search[1]: example.com\n"
"nameserver 192.0.2.1\n" "nameserver 192.0.2.1\n"
}, },
{.name = "whitespace", {.name = "whitespace",
@ -403,16 +430,46 @@ struct test_case test_cases[] =
" (trailing whitespace,\n" " (trailing whitespace,\n"
"# missing newline at end of file).\n" "# missing newline at end of file).\n"
"\n" "\n"
"domain example.net\n"
";search commented out\n" ";search commented out\n"
"search corp.example.com\texample.com\n" "search corp.example.com\texample.com \n"
"#nameserver 192.0.2.3\n" "#nameserver 192.0.2.3\n"
"nameserver 192.0.2.1 \n" "nameserver 192.0.2.1 \n"
"nameserver 192.0.2.2", /* No \n at end of file. */ "nameserver 192.0.2.2", /* No \n at end of file. */
.expected = "search corp.example.com example.com\n" .expected = "search corp.example.com example.com\n"
"; search[0]: corp.example.com\n"
"; search[1]: example.com\n"
"nameserver 192.0.2.1\n" "nameserver 192.0.2.1\n"
"nameserver 192.0.2.2\n" "nameserver 192.0.2.2\n"
}, },
{.name = "domain",
.conf = "domain example.net\n"
"nameserver 192.0.2.1\n",
.expected = "search example.net\n"
"; search[0]: example.net\n"
"nameserver 192.0.2.1\n"
},
{.name = "domain space",
.conf = "domain example.net \n"
"nameserver 192.0.2.1\n",
.expected = "search example.net\n"
"; search[0]: example.net\n"
"nameserver 192.0.2.1\n"
},
{.name = "domain tab",
.conf = "domain example.net\t\n"
"nameserver 192.0.2.1\n",
.expected = "search example.net\n"
"; search[0]: example.net\n"
"nameserver 192.0.2.1\n"
},
{.name = "domain override",
.conf = "search example.com example.org\n"
"nameserver 192.0.2.1\n"
"domain example.net", /* No \n at end of file. */
.expected = "search example.net\n"
"; search[0]: example.net\n"
"nameserver 192.0.2.1\n"
},
{.name = "option values, multiple servers", {.name = "option values, multiple servers",
.conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n" .conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
"domain example.net\n" "domain example.net\n"
@ -423,6 +480,8 @@ struct test_case test_cases[] =
"nameserver 192.0.2.2\n", "nameserver 192.0.2.2\n",
.expected = "options ndots:3 timeout:19 attempts:5 inet6 edns0\n" .expected = "options ndots:3 timeout:19 attempts:5 inet6 edns0\n"
"search corp.example.com example.com\n" "search corp.example.com example.com\n"
"; search[0]: corp.example.com\n"
"; search[1]: example.com\n"
"nameserver 192.0.2.1\n" "nameserver 192.0.2.1\n"
"nameserver ::1\n" "nameserver ::1\n"
"nameserver 192.0.2.2\n" "nameserver 192.0.2.2\n"
@ -432,6 +491,7 @@ struct test_case test_cases[] =
"search example.com\n", "search example.com\n",
.expected = "options ndots:15 timeout:30 attempts:5 use-vc\n" .expected = "options ndots:15 timeout:30 attempts:5 use-vc\n"
"search example.com\n" "search example.com\n"
"; search[0]: example.com\n"
"nameserver 127.0.0.1\n" "nameserver 127.0.0.1\n"
}, },
{.name = "repeated directives", {.name = "repeated directives",
@ -443,6 +503,7 @@ struct test_case test_cases[] =
"search\n", "search\n",
.expected = "options ndots:2 use-vc edns0\n" .expected = "options ndots:2 use-vc edns0\n"
"search example.org\n" "search example.org\n"
"; search[0]: example.org\n"
"nameserver 127.0.0.1\n" "nameserver 127.0.0.1\n"
}, },
{.name = "many name servers, sortlist", {.name = "many name servers, sortlist",
@ -459,6 +520,10 @@ struct test_case test_cases[] =
"nameserver 192.0.2.8\n", "nameserver 192.0.2.8\n",
.expected = "options single-request\n" .expected = "options single-request\n"
"search example.org example.com example.net corp.example.com\n" "search example.org example.com example.net corp.example.com\n"
"; search[0]: example.org\n"
"; search[1]: example.com\n"
"; search[2]: example.net\n"
"; search[3]: corp.example.com\n"
"sortlist 192.0.2.0/255.255.255.0\n" "sortlist 192.0.2.0/255.255.255.0\n"
"nameserver 192.0.2.1\n" "nameserver 192.0.2.1\n"
"nameserver 192.0.2.2\n" "nameserver 192.0.2.2\n"
@ -480,6 +545,11 @@ struct test_case test_cases[] =
.expected = "options single-request\n" .expected = "options single-request\n"
"search example.org example.com example.net corp.example.com" "search example.org example.com example.net corp.example.com"
" legacy.example.com\n" " legacy.example.com\n"
"; search[0]: example.org\n"
"; search[1]: example.com\n"
"; search[2]: example.net\n"
"; search[3]: corp.example.com\n"
"; search[4]: legacy.example.com\n"
"sortlist 192.0.2.0/255.255.255.0\n" "sortlist 192.0.2.0/255.255.255.0\n"
"nameserver 192.0.2.1\n" "nameserver 192.0.2.1\n"
"nameserver 2001:db8::2\n" "nameserver 2001:db8::2\n"
@ -490,6 +560,7 @@ struct test_case test_cases[] =
"nameserver 192.0.2.2:5353\n" "nameserver 192.0.2.2:5353\n"
"nameserver 192.0.2.3 5353\n", "nameserver 192.0.2.3 5353\n",
.expected = "search example.com\n" .expected = "search example.com\n"
"; search[0]: example.com\n"
"nameserver 192.0.2.1\n" "nameserver 192.0.2.1\n"
"nameserver 192.0.2.3\n" "nameserver 192.0.2.3\n"
}, },
@ -498,9 +569,42 @@ struct test_case test_cases[] =
"nameserver 192.0.2.1\n", "nameserver 192.0.2.1\n",
.expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n" .expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n"
"search example.com\n" "search example.com\n"
"; search[0]: example.com\n"
"nameserver 192.0.2.1\n", "nameserver 192.0.2.1\n",
.res_options = "attempts:5 ndots:3 edns0 ", .res_options = "attempts:5 ndots:3 edns0 ",
}, },
{.name = "many search list entries (bug 19569)",
.conf = "nameserver 192.0.2.1\n"
"search corp.example.com support.example.com"
" community.example.org wan.example.net vpn.example.net"
" example.com example.org example.net\n",
.expected = "search corp.example.com support.example.com"
" community.example.org wan.example.net vpn.example.net example.com\n"
"; search[0]: corp.example.com\n"
"; search[1]: support.example.com\n"
"; search[2]: community.example.org\n"
"; search[3]: wan.example.net\n"
"; search[4]: vpn.example.net\n"
"; search[5]: example.com\n"
"; search[6]: example.org\n"
"; search[7]: example.net\n"
"nameserver 192.0.2.1\n",
},
{.name = "very long search list entries (bug 21475)",
.conf = "nameserver 192.0.2.1\n"
"search example.com "
#define H63 "this-host-name-is-longer-than-yours-yes-I-really-really-mean-it"
#define D63 "this-domain-name-is-as-long-as-the-previous-name--63-characters"
" " H63 "." D63 ".example.org"
" " H63 "." D63 ".example.net\n",
.expected = "search example.com " H63 "." D63 ".example.org\n"
"; search[0]: example.com\n"
"; search[1]: " H63 "." D63 ".example.org\n"
"; search[2]: " H63 "." D63 ".example.net\n"
#undef H63
#undef D63
"nameserver 192.0.2.1\n",
},
{ NULL } { NULL }
}; };