diff --git a/src/dns.c b/src/dns.c index f77fe8a6..2db616cf 100644 --- a/src/dns.c +++ b/src/dns.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "bio.h" #include "dns.h" @@ -2133,15 +2134,30 @@ hsk_dns_name_parse( for (j = off; j < off + c; j++) { uint8_t b = data[j]; - // Hack because we're - // using c-strings. - if (b == 0x00) - b = 0xff; - - // This allows for double-dots - // too easily in our design. - if (b == 0x2e) - b = 0xfe; + switch (b) { + // Escape special characters + case 0x2e /*.*/: + case 0x28 /*(*/: + case 0x29 /*)*/: + case 0x3b /*;*/: + case 0x20 /* */: + case 0x40 /*@*/: + case 0x22 /*"*/: + case 0x5c /*\\*/: + if (name) + sprintf(&name[noff], "\\%c", (char)b); + + noff += 2; + continue; + default: + if (b < 0x20 || b > 0x7e) { + if (name) + sprintf(&name[noff], "\\%03d", b); + + noff += 4; + continue; + } + } if (name) name[noff] = b; @@ -2224,15 +2240,59 @@ hsk_dns_name_serialize( int size; int i; char *s; + int size_adjust = 0; + bool escaped = false; + size_t max = strlen(name) - 1; for (s = (char *)name, i = 0; *s; s++, i++) { + // Check for escaped byte codes and adjust length measurement. + if (name[i] == '\\') { + // Check the next three bytes (if within string length) + // for three-digit code which must encode a one-byte value. + if (i + 3 <= max && + isdigit(name[i + 1]) && + isdigit(name[i + 2]) && + isdigit(name[i + 3])) { + uint16_t value = (name[i + 1] - 0x30) * 100; + value += (name[i + 2] - 0x30) * 10; + value += (name[i + 3] - 0x30); + + // Bad escape, byte code out of range. + if (value > 0xff) { + *len = off; + return false; + } + + // The next three characters don't count towards final size. + size_adjust += 2; + s += 2; + i += 2; + + // Escaped dot by byte code + if (value == 0x2e) + escaped = true; + } + + // Literal escaped dot + if (i + 1 <= max && name[i + 1] == 0x2e) + escaped = true; + + // Remove single slash and skip next character. + size_adjust += 1; + s += 1; + i += 1; + + continue; + } + if (name[i] == '.') { - if (i > 0 && name[i - 1] == '.') { + if (i > 0 && name[i - 1] == '.' && !escaped) { + // Multiple dots (escaped dot is ok) *len = off; return false; } - size = i - begin; + size = i - begin - size_adjust; if (size > HSK_DNS_MAX_LABEL) { *len = off; @@ -2272,14 +2332,35 @@ hsk_dns_name_serialize( int j; for (j = begin; j < i; j++) { char ch = name[j]; - - // 0xff -> NUL - if (ch == -1) - ch = '\0'; - - // 0xfe -> . - if (ch == -2) - ch = '.'; + // Check for escaped byte codes and process into output result + if (ch == '\\') { + // Check the next three bytes (if within label length) + // for three-digit code which must encode a one-byte value. + if (j + 3 <= i && + isdigit(name[j + 1]) && + isdigit(name[j + 2]) && + isdigit(name[j + 3])) { + // Compute byte from next three characters. + uint16_t value = (name[++j] - 0x30) * 100; + value += (name[++j] - 0x30) * 10; + value += (name[++j] - 0x30); + + // Bad escape, byte code out of range. + if (value > 0xff) { + *len = off; + return false; + } + + // Write + data[off++] = value; + continue; + } else { + // Remove single slash and write next character + // as long as there is a next character. + if (j + 1 < i) + ch = name[++j]; + } + } data[off++] = ch; } @@ -2288,7 +2369,10 @@ hsk_dns_name_serialize( } begin = i + 1; + size_adjust = 0; } + + escaped = false; } if (i > HSK_DNS_MAX_NAME) { diff --git a/test/data/name_serialization_vectors.h b/test/data/name_serialization_vectors.h new file mode 100644 index 00000000..f502a76b --- /dev/null +++ b/test/data/name_serialization_vectors.h @@ -0,0 +1,159 @@ +/* + * Types + */ + +typedef struct name_serializtion_vector { + char *name; + uint8_t expected_data[24]; + size_t expected_len; + bool success; + char *parsed; +} name_serializtion_vector_t; + + +/* + * Vectors + */ + +static const name_serializtion_vector_t name_serializtion_vectors[] = { + { + "abcdef.", + { + 0x06, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x00 + }, + 8, + true, + "abcdef." + }, + { + "abc.def.", + { + 0x03, 0x61, 0x62, 0x63, 0x03, 0x64, 0x65, 0x66, 0x00 + }, + 9, + true, + "abc.def." + }, + { + "abcdef\\000.", + { + 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x00, 0x00 + }, + 9, + true, + "abcdef\\000." + }, + { + "abcdef\\255.", + { + 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0xff, 0x00 + }, + 9, + true, + "abcdef\\255." + }, + { + "abcdef\\256.", + {}, + 0, + false, // bad escape (value > 0xff) + NULL + }, + { + "abcdef\\LOL.", + { + 0x09, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x4c, 0x4f, 0x4c, 0x00 + }, + 11, + true, + "abcdefLOL." + }, + { + "abc\\031def.", + { + 0x07, 0x61, 0x62, 0x63, 0x1f, 0x64, 0x65, 0x66, 0x00 + }, + 9, + true, + "abc\\031def." + }, + { + "abc\\\\def.", + { + 0x07, 0x61, 0x62, 0x63, 0x5c, 0x64, 0x65, 0x66, 0x00 + }, + 9, + true, + "abc\\\\def." + }, + { + "\\999.", + {}, + 0, + false, // bad escape (value > 0xff) + NULL + }, + { + "\\\\999.", + { + 0x04, 0x5c, 0x39, 0x39, 0x39, 0x00 + }, + 6, + true, + "\\\\999." + }, + { + "\\\\222.", + { + 0x04, 0x5c, 0x32, 0x32, 0x32, 0x00 + }, + 6, + true, + "\\\\222." + }, + { + "abc\\\\999.", + { + 0x07, 0x61, 0x62, 0x63, 0x5c, 0x39, 0x39, 0x39, 0x00 + }, + 9, + true, + "abc\\\\999." + }, + { + "abc\\\\99.", + { + 0x06, 0x61, 0x62, 0x63, 0x5c, 0x39, 0x39, 0x00 + }, + 8, + true, + "abc\\\\99." + }, + { + "abc\\\\.", + { + 0x04, 0x61, 0x62, 0x63, 0x5c, 0x00 + }, + 6, + true, + "abc\\\\." + }, + { + "\\..", + { + 0x01, 0x2e, 0x00 + }, + 3, + true, + "\\.." + }, + { + "\\046.", + { + 0x01, 0x2e, 0x00 + }, + 3, + true, + "\\.." + } +}; diff --git a/test/hnsd-test.c b/test/hnsd-test.c index 7ec7b071..8ac9c00d 100644 --- a/test/hnsd-test.c +++ b/test/hnsd-test.c @@ -2,17 +2,32 @@ #include "base32.h" #include "resource.h" #include "resource.c" +#include "dns.h" +#include "dns.c" +#include "data/name_serialization_vectors.h" + +#define ARRAY_SIZE(x) ((sizeof(x))/(sizeof(x[0]))) + +/** + * UTILITY + */ void print_array(uint8_t *arr, size_t size){ for (int i = 0; i < size; i++) { - printf("%x", arr[i]); + printf("%02x", arr[i]); } printf("\n"); } +/* + * TESTS + */ + void test_base32() { + printf("test_base32\n"); + const char *str = "5l6tm80"; const uint8_t expected[4] = {45, 77, 219, 32}; @@ -29,6 +44,8 @@ test_base32() { void test_pointer_to_ip() { + printf("test_pointer_to_ip\n"); + const char *str4 = "_5l6tm80._synth"; const uint8_t expected4[4] = {45, 77, 219, 32}; uint8_t ip4[4]; @@ -60,11 +77,70 @@ test_pointer_to_ip() { assert(family6 == HSK_DNS_AAAA); } +void +test_name_serialize() { + printf("test_name_serialize\n"); + + for (int i = 0; i < ARRAY_SIZE(name_serializtion_vectors); i++) { + name_serializtion_vector_t name_serializtion_vector = name_serializtion_vectors[i]; + + uint8_t data[24] = {0}; + int len = 0; + + printf(" %s\n", name_serializtion_vector.name); + + bool success = hsk_dns_name_serialize( + name_serializtion_vector.name, + data, + &len, + NULL + ); + + assert(name_serializtion_vector.success == success); + assert(len == name_serializtion_vector.expected_len); + assert(memcmp(data, name_serializtion_vector.expected_data, len) == 0); + } +} + +void +test_name_parse() { + printf("test_name_parse\n"); + + for (int i = 0; i < ARRAY_SIZE(name_serializtion_vectors); i++) { + name_serializtion_vector_t name_serializtion_vector = name_serializtion_vectors[i]; + + char name[255]; + + if (!name_serializtion_vector.parsed) + continue; + + printf(" %s\n", name_serializtion_vector.name); + + uint8_t *ptr = (uint8_t *)&name_serializtion_vector.expected_data; + size_t len = name_serializtion_vector.expected_len; + + int ret = hsk_dns_name_parse( + (uint8_t **)&ptr, + &len, + NULL, + name + ); + assert(ret == strlen(name_serializtion_vector.parsed)); + assert(strcmp(name_serializtion_vector.parsed, name) == 0); + } +} + +/* + * TEST RUNNER + */ + int main() { printf("Testing hnsd...\n"); test_base32(); test_pointer_to_ip(); + test_name_serialize(); + test_name_parse(); printf("ok\n");