From 3f853f22c87f0b671c0366eb290919719fa56c0e Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Sat, 1 Jul 2017 00:53:05 +0200 Subject: [PATCH] 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. --- ChangeLog | 26 +++ NEWS | 8 + resolv/res_init.c | 222 +++++++++++++++++--------- resolv/res_query.c | 11 +- resolv/resolv_conf.c | 65 +++++++- resolv/resolv_conf.h | 4 + resolv/resolv_context.h | 19 +++ resolv/tst-resolv-res_init-skeleton.c | 108 ++++++++++++- 8 files changed, 382 insertions(+), 81 deletions(-) diff --git a/ChangeLog b/ChangeLog index db534332da..006112603a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2017-06-30 Florian Weimer + + [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 Add extended resolver state/configuration (struct resolv_conf). diff --git a/NEWS b/NEWS index df6f394180..bf2b5dfa18 100644 --- a/NEWS +++ b/NEWS @@ -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, diff --git a/resolv/res_init.c b/resolv/res_init.c index 659d3ea81f..5941d37f32 100644 --- a/resolv/res_init.c +++ b/resolv/res_init.c @@ -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 + +/* 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) { diff --git a/resolv/res_query.c b/resolv/res_query.c index 33249e36f5..ebbe5a6a4e 100644 --- a/resolv/res_query.c +++ b/resolv/res_query.c @@ -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 diff --git a/resolv/resolv_conf.c b/resolv/resolv_conf.c index cabe69cf1b..76d55fc32c 100644 --- a/resolv/resolv_conf.c +++ b/resolv/resolv_conf.c @@ -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)); diff --git a/resolv/resolv_conf.h b/resolv/resolv_conf.h index 48f92d6d57..80a0b93f94 100644 --- a/resolv/resolv_conf.h +++ b/resolv/resolv_conf.h @@ -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 diff --git a/resolv/resolv_context.h b/resolv/resolv_context.h index ff9ef2c7fe..0f4d47d26d 100644 --- a/resolv/resolv_context.h +++ b/resolv/resolv_context.h @@ -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 diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c index ce206f52c4..cea14569b8 100644 --- a/resolv/tst-resolv-res_init-skeleton.c +++ b/resolv/tst-resolv-res_init-skeleton.c @@ -25,6 +25,7 @@ #include #include #include /* For DEPRECATED_RES_USE_INET6. */ +#include #include #include #include @@ -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 } };