From ab61f95f16558033ac38c06d1175d851a17dccae Mon Sep 17 00:00:00 2001 From: dumbbell Date: Thu, 26 Jan 2012 22:01:05 +0000 Subject: [PATCH] MFC r228259: Support domain-search in dhclient(8) The "domain-search" option (option 119) allows a DHCP server to publish a list of implicit domain suffixes used during name lookup. This option is described in RFC 3397. For instance, if the domain-search option says: ".example.org .example.com" and one wants to resolve "foobar", the resolver will try: 1. "foobar.example.org" 2. "foobar.example.com" The file /etc/resolv.conf is updated with a "search" directive if the DHCP server provides "domain-search". A regression test suite is included in this patch under tools/regression/sbin/dhclient. PR: bin/151940 Sponsored by: Yakaz (http://www.yakaz.com) MFC r229000: Invalid Domain Search option isn't considered as a fatal error In the original Domain Search option patch, an invalid option value would cause the whole lease to be rejected. However, DHCP servers who emit such an invalid value are more common than I thought. With this new patch, just the option is rejected, not the entire lease. PR: bin/163431 Submitted by: Fabian Keil (earlier version) Reviewed by: Fabian Keil Sponsored by: Yakaz (http://www.yakaz.com) MFC r229001: Adapt testsuite following change in Domain Search error handling In this testsuite, warning() and error() have the same behaviour. PR: bin/163431 Sponsored by: Yakaz (http://www.yakaz.com) git-svn-id: svn://svn.freebsd.org/base/stable/8@230603 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f --- sbin/dhclient/clparse.c | 2 + sbin/dhclient/dhclient-script | 4 +- sbin/dhclient/dhclient.c | 1 + sbin/dhclient/dhcp-options.5 | 4 + sbin/dhclient/dhcp.h | 1 + sbin/dhclient/options.c | 174 ++++++++++ sbin/dhclient/tables.c | 5 +- tools/regression/sbin/Makefile | 2 +- tools/regression/sbin/dhclient/Makefile | 16 + tools/regression/sbin/dhclient/fake.c | 64 ++++ .../sbin/dhclient/option-domain-search.c | 328 ++++++++++++++++++ 11 files changed, 597 insertions(+), 4 deletions(-) create mode 100644 tools/regression/sbin/dhclient/Makefile create mode 100644 tools/regression/sbin/dhclient/fake.c create mode 100644 tools/regression/sbin/dhclient/option-domain-search.c diff --git a/sbin/dhclient/clparse.c b/sbin/dhclient/clparse.c index 5d7084e74..7c1e74fca 100644 --- a/sbin/dhclient/clparse.c +++ b/sbin/dhclient/clparse.c @@ -100,6 +100,8 @@ read_client_conf(void) DHO_DOMAIN_NAME_SERVERS; top_level_config.requested_options [top_level_config.requested_option_count++] = DHO_HOST_NAME; + top_level_config.requested_options + [top_level_config.requested_option_count++] = DHO_DOMAIN_SEARCH; if ((cfile = fopen(path_dhclient_conf, "r")) != NULL) { do { diff --git a/sbin/dhclient/dhclient-script b/sbin/dhclient/dhclient-script index cda844ffb..a544a917a 100644 --- a/sbin/dhclient/dhclient-script +++ b/sbin/dhclient/dhclient-script @@ -201,7 +201,9 @@ add_new_resolv_conf() { local tmpres=/var/run/resolv.conf.${interface} rm -f $tmpres - if [ -n "$new_domain_name" ]; then + if [ -n "$new_domain_search" ]; then + echo "search $new_domain_search" >>$tmpres + elif [ -n "$new_domain_name" ]; then echo "search $new_domain_name" >>$tmpres fi diff --git a/sbin/dhclient/dhclient.c b/sbin/dhclient/dhclient.c index afb6d6364..ab4fb35bc 100644 --- a/sbin/dhclient/dhclient.c +++ b/sbin/dhclient/dhclient.c @@ -2368,6 +2368,7 @@ check_option(struct client_lease *l, int option) } return (1); case DHO_DOMAIN_NAME: + case DHO_DOMAIN_SEARCH: if (!res_hnok(sbuf)) { if (!check_search(sbuf)) { warning("Bogus domain search list %d: %s (%s)", diff --git a/sbin/dhclient/dhcp-options.5 b/sbin/dhclient/dhcp-options.5 index 37356062c..1405839a9 100644 --- a/sbin/dhclient/dhcp-options.5 +++ b/sbin/dhclient/dhcp-options.5 @@ -265,6 +265,10 @@ character set. .It Ic option domain-name Ar string ; This option specifies the domain name that the client should use when resolving hostnames via the Domain Name System. +.It Ic option domain-search Ar string ; +This option specifies a list of domain names that the client should use +when resolving hostnames via the Domain Name System. This option is +defined in RFC 3397. .It Ic option swap-server Ar ip-address ; This specifies the IP address of the client's swap server. .It Ic option root-path Ar string ; diff --git a/sbin/dhclient/dhcp.h b/sbin/dhclient/dhcp.h index e4fa9d174..6e7d1a743 100644 --- a/sbin/dhclient/dhcp.h +++ b/sbin/dhclient/dhcp.h @@ -169,6 +169,7 @@ struct dhcp_packet { #define DHO_STREETTALK_SERVER 75 #define DHO_STREETTALK_DA_SERVER 76 #define DHO_DHCP_USER_CLASS_ID 77 +#define DHO_DOMAIN_SEARCH 119 #define DHO_CLASSLESS_ROUTES 121 #define DHO_END 255 diff --git a/sbin/dhclient/options.c b/sbin/dhclient/options.c index 09aa4d8ff..17643e767 100644 --- a/sbin/dhclient/options.c +++ b/sbin/dhclient/options.c @@ -55,6 +55,10 @@ void parse_options(struct packet *); void parse_option_buffer(struct packet *, unsigned char *, int); int store_options(unsigned char *, int, struct tree_cache **, unsigned char *, int, int, int, int); +void expand_domain_search(struct packet *packet); +int find_search_domain_name_len(struct option_data *option, int *offset); +void expand_search_domain_name(struct option_data *option, int *offset, + unsigned char **domain_search); /* @@ -94,6 +98,11 @@ parse_options(struct packet *packet) (unsigned char *)packet->raw->sname, sizeof(packet->raw->sname)); } + + /* Expand DHCP Domain Search option. */ + if (packet->options_valid) { + expand_domain_search(packet); + } } /* @@ -193,6 +202,171 @@ parse_option_buffer(struct packet *packet, packet->options_valid = 1; } +/* + * Expand DHCP Domain Search option. The value of this option is + * encoded like DNS' list of labels. See: + * RFC 3397 + * RFC 1035 + */ +void +expand_domain_search(struct packet *packet) +{ + int offset, expanded_len, next_domain_len; + struct option_data *option; + unsigned char *domain_search, *cursor; + + if (packet->options[DHO_DOMAIN_SEARCH].data == NULL) + return; + + option = &packet->options[DHO_DOMAIN_SEARCH]; + + /* Compute final expanded length. */ + expanded_len = 0; + offset = 0; + while (offset < option->len) { + next_domain_len = find_search_domain_name_len(option, &offset); + if (next_domain_len < 0) + /* The Domain Search option value is invalid. */ + return; + + /* We add 1 for the space between domain names. */ + expanded_len += next_domain_len + 1; + } + if (expanded_len > 0) + /* Remove 1 for the superfluous trailing space. */ + --expanded_len; + + domain_search = malloc(expanded_len + 1); + if (domain_search == NULL) + error("Can't allocate storage for expanded domain-search\n"); + + offset = 0; + cursor = domain_search; + while (offset < option->len) { + expand_search_domain_name(option, &offset, &cursor); + cursor[0] = ' '; + cursor++; + } + domain_search[expanded_len] = '\0'; + + free(option->data); + option->len = expanded_len; + option->data = domain_search; +} + +int +find_search_domain_name_len(struct option_data *option, int *offset) +{ + int domain_name_len, i, label_len, pointer, pointed_len; + + domain_name_len = 0; + + i = *offset; + while (i < option->len) { + label_len = option->data[i]; + if (label_len == 0) { + /* + * A zero-length label marks the end of this + * domain name. + */ + *offset = i + 1; + return (domain_name_len); + } else if (label_len & 0xC0) { + /* This is a pointer to another list of labels. */ + if (i + 1 >= option->len) { + /* The pointer is truncated. */ + warning("Truncated pointer in DHCP Domain " + "Search option."); + return (-1); + } + + pointer = ((label_len & ~(0xC0)) << 8) + + option->data[i + 1]; + if (pointer >= *offset) { + /* + * The pointer must indicates a prior + * occurance. + */ + warning("Invalid forward pointer in DHCP " + "Domain Search option compression."); + return (-1); + } + + pointed_len = find_search_domain_name_len(option, + &pointer); + domain_name_len += pointed_len; + + *offset = i + 2; + return (domain_name_len); + } + + if (i + label_len >= option->len) { + warning("Truncated label in DHCP Domain Search " + "option."); + return (-1); + } + + /* + * Update the domain name length with the length of the + * current label, plus a trailing dot ('.'). + */ + domain_name_len += label_len + 1; + + /* Move cursor. */ + i += label_len + 1; + } + + warning("Truncated DHCP Domain Search option."); + + return (-1); +} + +void +expand_search_domain_name(struct option_data *option, int *offset, + unsigned char **domain_search) +{ + int i, label_len, pointer; + unsigned char *cursor; + + /* + * This is the same loop than the function above + * (find_search_domain_name_len). Therefore, we remove checks, + * they're already done. Here, we just make the copy. + */ + i = *offset; + cursor = *domain_search; + while (i < option->len) { + label_len = option->data[i]; + if (label_len == 0) { + /* + * A zero-length label marks the end of this + * domain name. + */ + *offset = i + 1; + *domain_search = cursor; + return; + } else if (label_len & 0xC0) { + /* This is a pointer to another list of labels. */ + pointer = ((label_len & ~(0xC0)) << 8) + + option->data[i + 1]; + + expand_search_domain_name(option, &pointer, &cursor); + + *offset = i + 2; + *domain_search = cursor; + return; + } + + /* Copy the label found. */ + memcpy(cursor, option->data + i + 1, label_len); + cursor[label_len] = '.'; + + /* Move cursor. */ + i += label_len + 1; + cursor += label_len + 1; + } +} + /* * cons options into a big buffer, and then split them out into the * three separate buffers if needed. This allows us to cons up a set of diff --git a/sbin/dhclient/tables.c b/sbin/dhclient/tables.c index 81a9acc8e..c7bac57e4 100644 --- a/sbin/dhclient/tables.c +++ b/sbin/dhclient/tables.c @@ -184,7 +184,7 @@ struct option dhcp_options[256] = { { "option-116", "X", &dhcp_universe, 116 }, { "option-117", "X", &dhcp_universe, 117 }, { "option-118", "X", &dhcp_universe, 118 }, - { "option-119", "X", &dhcp_universe, 119 }, + { "domain-search", "t", &dhcp_universe, 119 }, { "option-120", "X", &dhcp_universe, 120 }, { "classless-routes", "BA", &dhcp_universe, 121 }, { "option-122", "X", &dhcp_universe, 122 }, @@ -400,12 +400,13 @@ unsigned char dhcp_option_default_priority_list[] = { DHO_IRC_SERVER, DHO_STREETTALK_SERVER, DHO_STREETTALK_DA_SERVER, + DHO_DOMAIN_SEARCH, /* Presently-undefined options... */ 62, 63, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, - 118, 119, 120, 122, 123, 124, 125, 126, 127, 128, 129, 130, + 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, diff --git a/tools/regression/sbin/Makefile b/tools/regression/sbin/Makefile index 33d158e7d..623ff62ee 100644 --- a/tools/regression/sbin/Makefile +++ b/tools/regression/sbin/Makefile @@ -1,5 +1,5 @@ # $FreeBSD$ -SUBDIR= growfs +SUBDIR= dhclient growfs .include diff --git a/tools/regression/sbin/dhclient/Makefile b/tools/regression/sbin/dhclient/Makefile new file mode 100644 index 000000000..7de3791e3 --- /dev/null +++ b/tools/regression/sbin/dhclient/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../../sbin/dhclient + +SRCS= alloc.c convert.c hash.c options.c tables.c \ + fake.c \ + option-domain-search.c + +CFLAGS+= -I${.CURDIR}/../../../../sbin/dhclient +LDADD= -lutil + +PROG= option-domain-search + +WARNS?= 2 + +.include diff --git a/tools/regression/sbin/dhclient/fake.c b/tools/regression/sbin/dhclient/fake.c new file mode 100644 index 000000000..c204d4940 --- /dev/null +++ b/tools/regression/sbin/dhclient/fake.c @@ -0,0 +1,64 @@ +/* $FreeBSD$ */ + +#include +#include +#include + +#include "dhcpd.h" + +extern jmp_buf env; + +void +error(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + + longjmp(env, 1); +} + +int +warning(char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + + /* + * The original warning() would return "ret" here. We do this to + * check warnings explicitely. + */ + longjmp(env, 1); +} + +int +note(char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + + return ret; +} + +void +bootp(struct packet *packet) +{ +} + +void +dhcp(struct packet *packet) +{ +} diff --git a/tools/regression/sbin/dhclient/option-domain-search.c b/tools/regression/sbin/dhclient/option-domain-search.c new file mode 100644 index 000000000..b79f9a560 --- /dev/null +++ b/tools/regression/sbin/dhclient/option-domain-search.c @@ -0,0 +1,328 @@ +/* $FreeBSD$ */ + +#include +#include + +#include "dhcpd.h" + +jmp_buf env; + +void expand_domain_search(struct packet *packet); + +void +no_option_present() +{ + int ret; + struct option_data option; + struct packet p; + + option.data = NULL; + option.len = 0; + p.options[DHO_DOMAIN_SEARCH] = option; + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (p.options[DHO_DOMAIN_SEARCH].len != 0 || + p.options[DHO_DOMAIN_SEARCH].data != NULL) + abort(); +} + +void +one_domain_valid() +{ + int ret; + struct packet p; + struct option_data *option; + + char *data = "\007example\003org\0"; + char *expected = "example.org."; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 13; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (option->len != strlen(expected) || + strcmp(option->data, expected) != 0) + abort(); + + free(option->data); +} + +void +one_domain_truncated1() +{ + int ret; + struct option_data *option; + struct packet p; + + char *data = "\007example\003org"; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 12; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (ret != 1) + abort(); + + free(option->data); +} + +void +one_domain_truncated2() +{ + int ret; + struct option_data *option; + struct packet p; + + char *data = "\007ex"; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 3; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (ret != 1) + abort(); + + free(option->data); +} + +void +two_domains_valid() +{ + int ret; + struct packet p; + struct option_data *option; + + char *data = "\007example\003org\0\007example\003com\0"; + char *expected = "example.org. example.com."; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 26; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (option->len != strlen(expected) || + strcmp(option->data, expected) != 0) + abort(); + + free(option->data); +} + +void +two_domains_truncated1() +{ + int ret; + struct option_data *option; + struct packet p; + + char *data = "\007example\003org\0\007example\003com"; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 25; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (ret != 1) + abort(); + + free(option->data); +} + +void +two_domains_truncated2() +{ + int ret; + struct option_data *option; + struct packet p; + + char *data = "\007example\003org\0\007ex"; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 16; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (ret != 1) + abort(); + + free(option->data); +} + +void +two_domains_compressed() +{ + int ret; + struct packet p; + struct option_data *option; + + char *data = "\007example\003org\0\006foobar\xc0\x08"; + char *expected = "example.org. foobar.org."; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 22; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (option->len != strlen(expected) || + strcmp(option->data, expected) != 0) + abort(); + + free(option->data); +} + +void +two_domains_infloop() +{ + int ret; + struct packet p; + struct option_data *option; + + char *data = "\007example\003org\0\006foobar\xc0\x0d"; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 22; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (ret != 1) + abort(); + + free(option->data); +} + +void +two_domains_forwardptr() +{ + int ret; + struct packet p; + struct option_data *option; + + char *data = "\007example\003org\xc0\x0d\006foobar\0"; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 22; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (ret != 1) + abort(); + + free(option->data); +} + +void +two_domains_truncatedptr() +{ + int ret; + struct packet p; + struct option_data *option; + + char *data = "\007example\003org\0\006foobar\xc0"; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 21; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (ret != 1) + abort(); + + free(option->data); +} + +void +multiple_domains_valid() +{ + int ret; + struct packet p; + struct option_data *option; + + char *data = + "\007example\003org\0\002cl\006foobar\003com\0\002fr\xc0\x10"; + + char *expected = "example.org. cl.foobar.com. fr.foobar.com."; + + option = &p.options[DHO_DOMAIN_SEARCH]; + option->len = 33; + option->data = malloc(option->len); + memcpy(option->data, data, option->len); + + ret = setjmp(env); + if (ret == 0) + expand_domain_search(&p); + + if (option->len != strlen(expected) || + strcmp(option->data, expected) != 0) + abort(); + + free(option->data); +} + +int +main(int argc, char *argv[]) +{ + + no_option_present(); + + one_domain_valid(); + one_domain_truncated1(); + one_domain_truncated2(); + + two_domains_valid(); + two_domains_truncated1(); + two_domains_truncated2(); + + two_domains_compressed(); + two_domains_infloop(); + two_domains_forwardptr(); + two_domains_truncatedptr(); + + multiple_domains_valid(); + + return (0); +} -- 2.45.0