[PATCH 8/9] gdhcp: Decode RFC3397 domain-search names

Peter Meerwald pmeerw at pmeerw.net
Tue Sep 10 06:46:19 PDT 2013


---
 gdhcp/client.c |  108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 gdhcp/common.c |    1 +
 gdhcp/common.h |    2 ++
 3 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/gdhcp/client.c b/gdhcp/client.c
index a3a4475..4ae03a4 100644
--- a/gdhcp/client.c
+++ b/gdhcp/client.c
@@ -1785,20 +1785,91 @@ static int sprint_nip(char *dest, const char *pre, const uint8_t *ip)
 	return sprintf(dest, "%s%u.%u.%u.%u", pre, ip[0], ip[1], ip[2], ip[3]);
 }
 
+/* copy a (uncompressed) domain name to dest; must validate first! */
+static void copy_domain_labels(char *dest, const uint8_t *block, const uint8_t *s)
+{
+	char *p = dest;
+	uint8_t len;
+
+	while ((len = *s++) != 0) {
+		if ((len & 0xc0) == 0xc0) {
+			s = &block[((len & 0x3f) << 8) | *s];
+			continue;
+		}
+		if (p != dest)
+			*p++ = '.';
+
+		memcpy(p, s, len);
+		p += len;
+		s += len;
+	}
+}
+
+/* validate and compute the length of a (uncompressed) domain name */
+static int domain_labels_length(const uint8_t *block, const uint8_t *block_end, const uint8_t *s)
+{
+	int total_len = 0;
+	bool subsequent = false;
+	int seen = 0;
+	uint8_t len;
+
+	if (!s)
+		return -1;
+
+	while ((len = *s++) != 0) {
+		switch (len & 0xc0) {
+		case 0xc0: { /* compression, see RFC1035 and RFC3397 */
+			int offset;
+
+			if (s >= block_end)
+				return -1;
+
+			offset = ((len & 0x3f) << 8) | *s;
+			s = &block[offset]; /* continue at offset with suffix */
+
+			seen += 2; /* to avoid infinite loops */
+			if (seen >= (block_end - block))
+				return -1;
+			break;
+		}
+		case 0x00:
+			if (subsequent) total_len++; /* dot inbetween labels */
+			else subsequent = true;
+
+			s += len;
+			total_len += len;
+			seen += len + 1;
+			break;
+		default:
+			return -1;
+		}
+
+		if (s < block || s > block_end)
+			return -1;
+	}
+
+	return total_len;
+}
+
 /* Create "opt_value1 option_value2 ..." string */
 static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type)
 {
 	unsigned upper_length;
-	int len, optlen;
+	int len = option[OPT_LEN - OPT_DATA], optlen;
 	char *dest, *ret;
+	uint8_t *block = option, *block_end = block + len;
+	uint8_t *domain_start = NULL;
 
-	len = option[OPT_LEN - OPT_DATA];
 	type &= OPTION_TYPE_MASK;
 	optlen = dhcp_option_lengths[type];
 	if (optlen == 0)
 		return NULL;
-	upper_length = len_of_option_as_string[type] *
+	if (type == OPTION_DOMAIN_STRINGS)
+		upper_length = 1024; /* maximum output for compressed input */
+	else
+		upper_length = len_of_option_as_string[type] *
 			((unsigned)len / (unsigned)optlen);
+
 	dest = ret = g_malloc(upper_length + 1);
 	if (!ret)
 		return NULL;
@@ -1818,6 +1889,37 @@ static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type)
 			dest += sprintf(dest, "%u", val_u32);
 			break;
 		}
+		case OPTION_DOMAIN_STRINGS: {
+			uint8_t label_len = *option;
+			if (label_len == 0) { /* regular end of a domain name */
+				int domain_len = domain_labels_length(block, block_end, domain_start);
+				if (domain_len > 0 && domain_len <= upper_length) {
+					copy_domain_labels(dest, block, domain_start);
+					dest += domain_len;
+					upper_length -= domain_len;
+				}
+				domain_start = NULL;
+			} else if ((label_len & 0xc0) == 0xc0) { /* end of prefix with pointer to suffix */
+				if (!domain_start)
+					domain_start = option;
+				int domain_len = domain_labels_length(block, block_end, domain_start);
+				if (domain_len > 0 && domain_len <= upper_length) {
+					copy_domain_labels(dest, block, domain_start);
+					dest += domain_len;
+					upper_length -= domain_len;
+				}
+				option += 1;
+				len -= 1;
+				domain_start = NULL;
+			} else { /* part of a domain name, no output */
+				if (!domain_start)
+					domain_start = option;
+				option += label_len + 1;
+				len -= label_len + 1;
+				continue;
+			}
+			break;
+		}
 		case OPTION_STRING:
 			memcpy(dest, option, len);
 			dest[len] = '\0';
diff --git a/gdhcp/common.c b/gdhcp/common.c
index 6f30591..aa4d98d 100644
--- a/gdhcp/common.c
+++ b/gdhcp/common.c
@@ -47,6 +47,7 @@ static const DHCPOption client_options[] = {
 	{ OPTION_STRING,		0x0f }, /* domain-name */
 	{ OPTION_IP,			0x2a }, /* ntp-servers */
 	{ OPTION_U32,			0x33 }, /* dhcp-lease-time */
+	{ OPTION_DOMAIN_STRINGS,	0x77 }, /* domain-search */
 	/* Options below will not be exposed to user */
 	{ OPTION_IP,			0x32 }, /* requested-ip */
 	{ OPTION_U8,			0x35 }, /* message-type */
diff --git a/gdhcp/common.h b/gdhcp/common.h
index e9bf5eb..691c57a 100644
--- a/gdhcp/common.h
+++ b/gdhcp/common.h
@@ -152,6 +152,7 @@ typedef enum {
 	OPTION_U8,
 	OPTION_U16,
 	OPTION_U32,
+	OPTION_DOMAIN_STRINGS,
 	OPTION_TYPE_MASK = 0x0f,
 } GDHCPOptionType;
 
@@ -164,6 +165,7 @@ typedef struct dhcp_option {
 static const uint8_t dhcp_option_lengths[] = {
 	[OPTION_IP]	= 4,
 	[OPTION_STRING]	= 1,
+	[OPTION_DOMAIN_STRINGS] = 1,
 	[OPTION_U8]	= 1,
 	[OPTION_U16]	= 2,
 	[OPTION_U32]	= 4,
-- 
1.7.9.5




More information about the connman mailing list