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>
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
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:
* 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 == '&';
}
/* 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
deallocation and error handling. Return true on success, false on
failure. */
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;
int nserv = 0; /* Number of nameservers read from file. */
bool have_serv6 = false;
bool haveenv = false;
bool havesearch = false;
int nsort = 0;
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. */
if ((cp = getenv ("LOCALDOMAIN")) != NULL)
{
strncpy (statp->defdname, cp, sizeof (statp->defdname) - 1);
statp->defdname[sizeof (statp->defdname) - 1] = '\0';
/* The code below splits the string in place. */
cp = __strdup (cp);
if (cp == NULL)
return false;
free (parser->search_list_store);
parser->search_list_store = cp;
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
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 (int n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++)
for (bool in_name = true; *cp != '\0'; cp++)
{
if (*cp == '\n')
break;
{
*cp = '\0';
break;
}
else if (*cp == ' ' || *cp == '\t')
{
*cp = 0;
n = 1;
*cp = '\0';
in_name = false;
}
else if (n > 0)
else if (!in_name)
{
*pp++ = cp;
n = 0;
havesearch = true;
search_list_add (&parser->search_list, cp);
in_name = 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) \
@ -210,7 +268,7 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
while (true)
{
{
ssize_t ret = __getline (buffer, &buffer_size, fp);
ssize_t ret = __getline (&parser->buffer, &buffer_size, fp);
if (ret <= 0)
{
if (_IO_ferror_unlocked (fp))
@ -221,73 +279,82 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
}
/* Skip comments. */
if (**buffer == ';' || **buffer == '#')
if (*parser->buffer == ';' || *parser->buffer == '#')
continue;
/* Read default domain name. */
if (MATCH (*buffer, "domain"))
if (MATCH (parser->buffer, "domain"))
{
if (haveenv)
/* LOCALDOMAIN overrides the configuration file. */
continue;
cp = *buffer + sizeof ("domain") - 1;
cp = parser->buffer + 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 = __strdup (cp);
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';
havesearch = false;
continue;
}
/* Set search list. */
if (MATCH (*buffer, "search"))
if (MATCH (parser->buffer, "search"))
{
if (haveenv)
/* LOCALDOMAIN overrides the configuration file. */
continue;
cp = *buffer + sizeof ("search") - 1;
cp = parser->buffer + 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';
{
char *p = strchr (cp, '\n');
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
of line. */
cp = statp->defdname;
pp = statp->dnsrch;
*pp++ = cp;
for (int n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++)
for (bool in_name = true; *cp != '\0'; cp++)
{
if (*cp == ' ' || *cp == '\t')
{
*cp = 0;
n = 1;
*cp = '\0';
in_name = false;
}
else if (n)
else if (!in_name)
{
*pp++ = cp;
n = 0;
search_list_add (&parser->search_list, cp);
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;
}
/* Read nameservers to query. */
if (MATCH (*buffer, "nameserver") && nserv < MAXNS)
if (MATCH (parser->buffer, "nameserver") && nserv < MAXNS)
{
struct in_addr a;
cp = *buffer + sizeof ("nameserver") - 1;
cp = parser->buffer + sizeof ("nameserver") - 1;
while (*cp == ' ' || *cp == '\t')
cp++;
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;
}
if (MATCH (*buffer, "sortlist"))
if (MATCH (parser->buffer, "sortlist"))
{
struct in_addr a;
cp = *buffer + sizeof ("sortlist") - 1;
cp = parser->buffer + sizeof ("sortlist") - 1;
while (nsort < MAXRESOLVSORT)
{
while (*cp == ' ' || *cp == '\t')
@ -379,9 +446,9 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
}
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;
}
}
@ -399,25 +466,29 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
statp->nsaddr.sin_port = htons (NAMESERVER_PORT);
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 (!havesearch)
if (search_list_size (&parser->search_list) == 0)
{
pp = statp->dnsrch;
*pp++ = statp->defdname;
*pp = NULL;
char *domain;
if (!domain_from_hostname (&domain))
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)
res_setoptions (statp, cp);
if (search_list_has_failed (&parser->search_list))
{
__set_errno (ENOMEM);
return false;
}
statp->options |= RES_INIT;
return true;
}
@ -453,13 +524,17 @@ __res_vinit (res_state statp, int preinit)
return -1;
}
char *buffer = NULL;
bool ok = res_vinit_1 (statp, preinit, fp, &buffer);
free (buffer);
struct resolv_conf_parser parser;
resolv_conf_parser_init (&parser);
bool ok = res_vinit_1 (statp, preinit, fp, &parser);
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);
if (conf == NULL)
ok = false;
@ -469,6 +544,7 @@ __res_vinit (res_state statp, int preinit)
__resolv_conf_put (conf);
}
}
resolv_conf_parser_free (&parser);
if (!ok)
{

View File

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

View File

@ -133,6 +133,34 @@ static bool
resolv_conf_matches (const struct __res_state *resp,
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;
}
@ -162,10 +190,17 @@ __resolv_conf_put (struct resolv_conf *conf)
struct resolv_conf *
__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. */
void *ptr;
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);
struct resolv_conf *conf
= alloc_buffer_alloc (&buffer, struct resolv_conf);
@ -178,6 +213,16 @@ __resolv_conf_allocate (const struct resolv_conf *init)
conf->__refcount = 1;
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));
return conf;
}
@ -186,6 +231,24 @@ __resolv_conf_allocate (const struct resolv_conf *init)
static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
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
initialization. */
assert (resolv_conf_matches (resp, conf));

View File

@ -35,6 +35,10 @@ struct resolv_conf
/* Reference counter. The object is deallocated once it reaches
zero. For internal use within resolv_conf only. */
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

View File

@ -93,6 +93,25 @@ struct resolv_context *__resolv_context_get_override (struct __res_state *)
__attribute__ ((nonnull (1), warn_unused_result));
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
context (mostly in response to cancellation, otherwise the
__resolv_context_get/__resolv_context_put pairing will already have

View File

@ -25,6 +25,7 @@
#include <gnu/lib-names.h>
#include <netdb.h>
#include <resolv/resolv-internal.h> /* For DEPRECATED_RES_USE_INET6. */
#include <resolv/resolv_context.h>
#include <stdio.h>
#include <stdlib.h>
#include <support/capture_subprocess.h>
@ -116,6 +117,11 @@ print_option_flag (FILE *fp, int *options, int flag, const char *name)
static void
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. */
{
/* 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')
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. */
if (resp->nsort > 0)
{
@ -224,6 +243,8 @@ print_resp (FILE *fp, res_state resp)
}
TEST_VERIFY (!ferror (fp));
__resolv_context_put (ctx);
}
/* Parameters of one test case. */
@ -368,11 +389,13 @@ struct test_case test_cases[] =
{.name = "empty file",
.conf = "",
.expected = "search example.com\n"
"; search[0]: example.com\n"
"nameserver 127.0.0.1\n"
},
{.name = "empty file with LOCALDOMAIN",
.conf = "",
.expected = "search example.net\n"
"; search[0]: example.net\n"
"nameserver 127.0.0.1\n",
.localdomain = "example.net",
},
@ -380,6 +403,7 @@ struct test_case test_cases[] =
.conf = "",
.expected = "options attempts:5 edns0\n"
"search example.com\n"
"; search[0]: example.com\n"
"nameserver 127.0.0.1\n",
.res_options = "edns0 attempts:5",
},
@ -387,6 +411,7 @@ struct test_case test_cases[] =
.conf = "",
.expected = "options attempts:5 edns0\n"
"search example.org\n"
"; search[0]: example.org\n"
"nameserver 127.0.0.1\n",
.localdomain = "example.org",
.res_options = "edns0 attempts:5",
@ -396,6 +421,8 @@ struct test_case test_cases[] =
"search corp.example.com example.com\n"
"nameserver 192.0.2.1\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"
},
{.name = "whitespace",
@ -403,16 +430,46 @@ struct test_case test_cases[] =
" (trailing whitespace,\n"
"# missing newline at end of file).\n"
"\n"
"domain example.net\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.1 \n"
"nameserver 192.0.2.2", /* No \n at end of file. */
.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.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",
.conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
"domain example.net\n"
@ -423,6 +480,8 @@ struct test_case test_cases[] =
"nameserver 192.0.2.2\n",
.expected = "options ndots:3 timeout:19 attempts:5 inet6 edns0\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 ::1\n"
"nameserver 192.0.2.2\n"
@ -432,6 +491,7 @@ struct test_case test_cases[] =
"search example.com\n",
.expected = "options ndots:15 timeout:30 attempts:5 use-vc\n"
"search example.com\n"
"; search[0]: example.com\n"
"nameserver 127.0.0.1\n"
},
{.name = "repeated directives",
@ -443,6 +503,7 @@ struct test_case test_cases[] =
"search\n",
.expected = "options ndots:2 use-vc edns0\n"
"search example.org\n"
"; search[0]: example.org\n"
"nameserver 127.0.0.1\n"
},
{.name = "many name servers, sortlist",
@ -459,6 +520,10 @@ struct test_case test_cases[] =
"nameserver 192.0.2.8\n",
.expected = "options single-request\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"
"nameserver 192.0.2.1\n"
"nameserver 192.0.2.2\n"
@ -480,6 +545,11 @@ struct test_case test_cases[] =
.expected = "options single-request\n"
"search example.org example.com example.net corp.example.com"
" 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"
"nameserver 192.0.2.1\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.3 5353\n",
.expected = "search example.com\n"
"; search[0]: example.com\n"
"nameserver 192.0.2.1\n"
"nameserver 192.0.2.3\n"
},
@ -498,9 +569,42 @@ struct test_case test_cases[] =
"nameserver 192.0.2.1\n",
.expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n"
"search example.com\n"
"; search[0]: example.com\n"
"nameserver 192.0.2.1\n",
.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 }
};