From ff8355fd0adf45a0a0be9afa0e0791db4f1251ff Mon Sep 17 00:00:00 2001 From: dumbbell Date: Sun, 4 Dec 2011 14:44:31 +0000 Subject: 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) --- 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 | 166 +++++++++++ sbin/dhclient/tables.c | 5 +- tools/regression/sbin/Makefile | 2 +- tools/regression/sbin/dhclient/Makefile | 16 + tools/regression/sbin/dhclient/fake.c | 60 ++++ .../sbin/dhclient/option-domain-search.c | 328 +++++++++++++++++++++ 11 files changed, 585 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 5d7084e..7c1e74f 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 826d83c..b066400 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 0521730..4f2405a 100644 --- a/sbin/dhclient/dhclient.c +++ b/sbin/dhclient/dhclient.c @@ -2401,6 +2401,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 3735606..1405839 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 e4fa9d1..6e7d1a7 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 09aa4d8..3f87028 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); + } } /* @@ -194,6 +203,163 @@ parse_option_buffer(struct packet *packet, } /* + * 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; + 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) { + /* We add 1 for the space between domain names. */ + expanded_len += + find_search_domain_name_len(option, &offset) + 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. */ + error("Truncated pointer in DHCP Domain " + "Search option."); + } + + pointer = ((label_len & ~(0xC0)) << 8) + + option->data[i + 1]; + if (pointer >= *offset) { + /* + * The pointer must indicates a prior + * occurance. + */ + error("Invalid forward pointer in DHCP Domain " + "Search option compression."); + } + + 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) { + error("Truncated label in DHCP Domain Search option."); + } + + /* + * 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; + } + + error("Truncated DHCP Domain Search option."); + + return (0); +} + +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 * vendor options using the same routine. diff --git a/sbin/dhclient/tables.c b/sbin/dhclient/tables.c index 81a9acc..c7bac57 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 33d158e..623ff62 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 0000000..7de3791 --- /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 0000000..91d57b6 --- /dev/null +++ b/tools/regression/sbin/dhclient/fake.c @@ -0,0 +1,60 @@ +/* $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"); + + return ret; +} + +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 0000000..b79f9a5 --- /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); +} -- cgit v1.1