diff --git a/src/common.c b/src/common.c index 7465eb5..37ae48d 100644 --- a/src/common.c +++ b/src/common.c @@ -404,6 +404,52 @@ check_topdomain(char *str, int allow_wildcard, char **errormsg) return 0; } +int +query_datalen(const char *qname, const char *topdomain) +{ + /* Return number of data bytes embedded in DNS query name, + * or -1 if domains do not match. + */ + int qpos = strlen(qname); + int tpos = strlen(topdomain); + if (tpos < 3 || qpos < tpos) { + /* Domain or query name too short */ + return -1; + } + /* Backward string compare */ + qpos--; + tpos--; + while (qpos >= 0) { + if (topdomain[tpos] == '*') { + /* Wild match, is first in topdomain */ + if (qname[qpos] == '*') { + /* Don't match against stars in query name */ + return -1; + } else if (qpos == 0 || qname[qpos-1] == '.') { + /* Reached start of query name or chunk separator */ + return qpos; + } + qpos--; + } else if (tolower(qname[qpos]) == tolower(topdomain[tpos])) { + /* Matching char, exclude wildcard in query name */ + if (tpos == 0) { + /* Fully matched domain */ + if (qpos == 0 || qname[qpos-1] == '.') { + /* Start of name or has dot before matching topdomain */ + return qpos; + } + /* Query name has longer chunk than topdomain */ + return -1; + } + tpos--; + qpos--; + } else { + return -1; + } + } + return -1; +} + #if defined(WINDOWS32) || defined(ANDROID) #ifndef ANDROID int diff --git a/src/common.h b/src/common.h index 87800ed..0f990c1 100644 --- a/src/common.h +++ b/src/common.h @@ -128,6 +128,8 @@ void read_password(char*, size_t); int check_topdomain(char *, int, char **); +int query_datalen(const char *qname, const char *topdomain); + #if defined(WINDOWS32) || defined(ANDROID) #ifndef ANDROID int inet_aton(const char *cp, struct in_addr *inp); diff --git a/tests/common.c b/tests/common.c index 277fd5a..0b1fcab 100644 --- a/tests/common.c +++ b/tests/common.c @@ -143,6 +143,52 @@ START_TEST(test_topdomain_wild) } END_TEST +START_TEST(test_query_datalen) +{ + char *topdomain = "r.foo.com"; + /* With data */ + ck_assert(query_datalen("foobar.r.foo.com", topdomain) == 7); + ck_assert(query_datalen("foobar.r.FoO.Com", topdomain) == 7); + ck_assert(query_datalen("foo.bar.r.FoO.Com", topdomain) == 8); + ck_assert(query_datalen(".r.foo.com", topdomain) == 1); + /* Without data */ + ck_assert(query_datalen("r.foo.com", topdomain) == 0); + ck_assert(query_datalen("R.foo.com", topdomain) == 0); + /* Shorter query name */ + ck_assert(query_datalen("foo.com", topdomain) == -1); + /* Mismatched query name */ + ck_assert(query_datalen("b.foo.com", topdomain) == -1); + ck_assert(query_datalen("*.foo.com", topdomain) == -1); + /* Query name overlaps topdomain, but is longer */ + ck_assert(query_datalen("bar.foo.com", topdomain) == -1); +} +END_TEST + +START_TEST(test_query_datalen_wild) +{ + char *topdomain = "*.foo.com"; + /* With data */ + ck_assert(query_datalen("foobar.a.foo.com", topdomain) == 7); + ck_assert(query_datalen("foobar.r.FoO.Com", topdomain) == 7); + ck_assert(query_datalen("foo.bar.r.FoO.Com", topdomain) == 8); + ck_assert(query_datalen("foo.Ab.foo.cOm", topdomain) == 4); + ck_assert(query_datalen("foo.Abcd.Foo.com", topdomain) == 4); + ck_assert(query_datalen("***.STARs.foo.com", topdomain) == 4); + ck_assert(query_datalen(".a.foo.com", topdomain) == 1); + ck_assert(query_datalen(".ab.foo.com", topdomain) == 1); + /* Without data */ + ck_assert(query_datalen("rr.foo.com", topdomain) == 0); + ck_assert(query_datalen("b.foo.com", topdomain) == 0); + ck_assert(query_datalen("B.foo.com", topdomain) == 0); + /* Shorter query name */ + ck_assert(query_datalen("foo.com", topdomain) == -1); + /* Wildcard part of query name matching topdomain */ + ck_assert(query_datalen("aa.*.foo.com", topdomain) == -1); + /* Mismatched query name */ + ck_assert(query_datalen("bar.r.boo.com", topdomain) == -1); +} +END_TEST + START_TEST(test_parse_format_ipv4) { char *host = "192.168.2.10"; @@ -245,6 +291,8 @@ test_common_create_tests() tcase_add_test(tc, test_topdomain_length); tcase_add_test(tc, test_topdomain_chunks); tcase_add_test(tc, test_topdomain_wild); + tcase_add_test(tc, test_query_datalen); + tcase_add_test(tc, test_query_datalen_wild); tcase_add_test(tc, test_parse_format_ipv4); tcase_add_test(tc, test_parse_format_ipv4_listen_all);