#include "dhcp-client-id-internal.h"
#include "dhcp-message.h"
#include "dhcp-protocol.h"
+#include "dns-def.h"
#include "dns-domain.h"
#include "errno-util.h"
#include "ether-addr-util.h"
return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret);
}
+int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret) {
+ int r;
+
+ assert(message);
+
+ /* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH. */
+
+ _cleanup_(iovec_done) struct iovec iov = {};
+ r = dhcp_message_get_option_alloc(message, code, &iov);
+ if (r < 0)
+ return r;
+
+ const uint8_t *buf = iov.iov_base;
+ size_t len = iov.iov_len;
+
+ _cleanup_strv_free_ char **names = NULL;
+ size_t n_names = 0;
+
+ _cleanup_free_ char *name = NULL;
+ size_t n = 0;
+
+ for (size_t pos = 0, jump_barrier = 0, next_chunk = 0; pos < len;) {
+ uint8_t c = buf[pos++];
+
+ if (c == 0) {
+ /* End of name */
+
+ if (!string_is_safe(name, /* flags= */ 0))
+ return -EBADMSG;
+
+ _cleanup_free_ char *normalized = NULL;
+ r = normalize_dns_name(name, &normalized);
+ if (r < 0)
+ return r;
+
+ r = strv_consume_with_size(&names, &n_names, TAKE_PTR(normalized));
+ if (r < 0)
+ return r;
+
+ if (next_chunk != 0)
+ pos = next_chunk;
+
+ next_chunk = 0;
+ jump_barrier = pos;
+
+ name = mfree(name);
+ n = 0;
+
+ } else if (c <= 63) {
+ /* Literal label */
+
+ const char *label = (const char*) (buf + pos);
+ pos += c;
+
+ if (pos >= len)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(name, n + 1 + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ if (n != 0)
+ name[n++] = '.';
+
+ r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+
+ } else if (FLAGS_SET(c, 0xc0)) {
+ /* Pointer */
+
+ if (pos >= len) /* pointer is 2 bytes, hence we need to read at least one more byte. */
+ return -EBADMSG;
+
+ /* Save the current location so we don't end up re-parsing what's parsed so far. */
+ if (next_chunk == 0)
+ next_chunk = pos + 1;
+
+ pos = ((size_t) (c & ~0xc0) << 8) | ((size_t) buf[pos]);
+
+ /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
+ if (pos >= jump_barrier)
+ return -EBADMSG;
+
+ jump_barrier = pos;
+
+ } else
+ return -EBADMSG;
+ }
+
+ if (!isempty(name)) /* trailing garbage?? Should not happen, but for safety. */
+ return -EBADMSG;
+
+ if (strv_isempty(names))
+ return -EBADMSG;
+
+ if (ret)
+ *ret = TAKE_PTR(names);
+ return 0;
+}
+
int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret) {
int r;
ASSERT_TRUE(iovw_equal(&iovw, &iovw2));
}
+static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) {
+ _cleanup_strv_free_ char **strv = NULL;
+ _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
+ ASSERT_OK(dhcp_message_new(&m));
+
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data));
+ ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv));
+ ASSERT_TRUE(strv_equal(strv, expected));
+
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH);
+ strv = strv_free(strv);
+
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len / 2, data));
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len - len / 2, data + len / 2));
+ ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv));
+ ASSERT_TRUE(strv_equal(strv, expected));
+}
+
+static void test_domains_fail(size_t len, const uint8_t *data) {
+ _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
+ ASSERT_OK(dhcp_message_new(&m));
+
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data));
+ ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, /* ret= */ NULL), EBADMSG);
+
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH);
+
+ _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1);
+ sip[0] = 0;
+ memcpy(sip + 1, data, len);
+
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip));
+ ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, /* ret= */ NULL), EBADMSG);
+}
+
+TEST(domains) {
+ /* simple */
+ static uint8_t simple[] = {
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ 4, 'h', 'o', 'g', 'e',
+ 3, 'f', 'o', 'o',
+ 0,
+ };
+ test_domains_one(ELEMENTSOF(simple), simple,
+ STRV_MAKE("example.com", "hoge.foo"));
+
+ /* compressed */
+ static uint8_t compressed[] = {
+ 4, 'h', 'o', 'g', 'e',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ 3, 'f', 'o', 'o',
+ 0xc0, 5,
+ 3, 'b', 'a', 'r',
+ 0xc0, 18,
+ 3, 'b', 'a', 'z',
+ 0xc0, 0,
+ };
+ test_domains_one(ELEMENTSOF(compressed), compressed,
+ STRV_MAKE("hoge.example.com", "foo.example.com", "bar.foo.example.com", "baz.hoge.example.com"));
+
+ /* invalid pointer */
+ static uint8_t invalid[] = {
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ 3, 'f', 'o', 'o',
+ 0xc0, 0xff,
+ };
+ test_domains_fail(ELEMENTSOF(invalid), invalid);
+
+ /* forward pointer */
+ static uint8_t forward[] = {
+ 4, 'h', 'o', 'g', 'e',
+ 0xc0, 11,
+ 3, 'f', 'o', 'o',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ };
+ test_domains_fail(ELEMENTSOF(forward), forward);
+
+ /* infinite loop */
+ static uint8_t loop1[] = {
+ 0xc0, 0x00,
+ };
+ test_domains_fail(ELEMENTSOF(loop1), loop1);
+
+ static uint8_t loop2[] = {
+ 0xc0, 0x02,
+ 0xc0, 0x00,
+ };
+ test_domains_fail(ELEMENTSOF(loop2), loop2);
+
+ static uint8_t loop3[] = {
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 0xc0, 0x00,
+ };
+ test_domains_fail(ELEMENTSOF(loop3), loop3);
+
+ /* unterminated */
+ static uint8_t unterminated[] = {
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ };
+ test_domains_fail(ELEMENTSOF(unterminated), unterminated);
+
+ /* truncated label */
+ static uint8_t truncated[] = {
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o',
+ };
+ test_domains_fail(ELEMENTSOF(truncated), truncated);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);