From cbe379ad6b52a538a4416a7cd992817e5637ccf9 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 21 Apr 2015 22:57:06 +0100 Subject: Handle domain names with '.' or /000 within labels. Only in DNSSEC mode, where we might need to validate or store such names. In none-DNSSEC mode, simply don't cache these, as before. --- src/dns-protocol.h | 4 ++++ src/dnsmasq.c | 15 +++++++++++++-- src/dnssec.c | 40 +++++++++++++++++++++++++++++++--------- src/rfc1035.c | 16 +++++++++++++++- src/util.c | 9 ++++++++- 5 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 16fade3..7f5d686 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -142,3 +142,7 @@ struct dns_header { #define ADD_RDLEN(header, pp, plen, len) \ (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1)) + +/* Escape character in our presentation format for names. + Cannot be '.' or /000 and must be !isprint() */ +#define NAME_ESCAPE 1 diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 20b15c0..19a6428 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -102,8 +102,19 @@ int main (int argc, char **argv) #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) { - daemon->keyname = safe_malloc(MAXDNAME); - daemon->workspacename = safe_malloc(MAXDNAME); + /* Note that both /000 and '.' are allowed within labels. These get + represented in presentation format using NAME_ESCAPE as an escape + character when in DNSSEC mode. + In theory, if all the characters in a name were /000 or + '.' or NAME_ESCAPE then all would have to be escaped, so the + presentation format would be twice as long as the spec. + + daemon->namebuff was previously allocated by the option-reading + code before we knew if we're in DNSSEC mode, so reallocate here. */ + free(daemon->namebuff); + daemon->namebuff = safe_malloc(MAXDNAME * 2); + daemon->keyname = safe_malloc(MAXDNAME * 2); + daemon->workspacename = safe_malloc(MAXDNAME * 2); } #endif diff --git a/src/dnssec.c b/src/dnssec.c index 05e0983..c116a7b 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -321,10 +321,18 @@ static int verify(struct blockdata *key_data, unsigned int key_len, unsigned cha thus generating names in canonical form. Calling to_wire followed by from_wire is almost an identity, except that the UC remains mapped to LC. + + Note that both /000 and '.' are allowed within labels. These get + represented in presentation format using NAME_ESCAPE as an escape + character. In theory, if all the characters in a name were /000 or + '.' or NAME_ESCAPE then all would have to be escaped, so the + presentation format would be twice as long as the spec (1024). + The buffers are all delcared as 2049 (allowing for the trailing zero) + for this reason. */ static int to_wire(char *name) { - unsigned char *l, *p, term; + unsigned char *l, *p, *q, term; int len; for (l = (unsigned char*)name; *l != 0; l = p) @@ -332,7 +340,10 @@ static int to_wire(char *name) for (p = l; *p != '.' && *p != 0; p++) if (*p >= 'A' && *p <= 'Z') *p = *p - 'A' + 'a'; - + else if (*p == NAME_ESCAPE) + for (q = p; *q; q++) + *q = *(q+1); + term = *p; if ((len = p - l) != 0) @@ -351,13 +362,23 @@ static int to_wire(char *name) /* Note: no compression allowed in input. */ static void from_wire(char *name) { - unsigned char *l; + unsigned char *l, *p, *last; int len; - + + for (last = (unsigned char *)name; *last != 0; last += *last+1); + for (l = (unsigned char *)name; *l != 0; l += len+1) { len = *l; memmove(l, l+1, len); + for (p = l; p < l + len; p++) + if (*p == '.' || *p == 0 || *p == NAME_ESCAPE) + { + memmove(p+1, p, 1 + last - p); + len++; + *p++ = NAME_ESCAPE; + } + l[len] = '.'; } @@ -645,7 +666,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int if (left1 != 0) memmove(buff1, buff1 + len1 - left1, left1); - if ((len1 = get_rdata(header, plen, end1, buff1 + left1, MAXDNAME - left1, &p1, &dp1)) == 0) + if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) { quit = 1; len1 = end1 - p1; @@ -656,7 +677,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int if (left2 != 0) memmove(buff2, buff2 + len2 - left2, left2); - if ((len2 = get_rdata(header, plen, end2, buff2 + left2, MAXDNAME - left2, &p2, &dp2)) == 0) + if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0) { quit = 1; len2 = end2 - p2; @@ -902,10 +923,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in end = p + rdlen; - /* canonicalise rdata and calculate length of same, use name buffer as workspace */ + /* canonicalise rdata and calculate length of same, use name buffer as workspace. + Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ cp = p; dp = rr_desc; - for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME, &cp, &dp)) != 0; len += seg); + for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); len += end - cp; len = htons(len); hash->update(ctx, 2, (unsigned char *)&len); @@ -913,7 +935,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in /* Now canonicalise again and digest. */ cp = p; dp = rr_desc; - while ((seg = get_rdata(header, plen, end, name, MAXDNAME, &cp, &dp))) + while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) hash->update(ctx, seg, (unsigned char *)name); if (cp != end) hash->update(ctx, end - cp, cp); diff --git a/src/rfc1035.c b/src/rfc1035.c index a995ab5..19fecc8 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -128,6 +128,15 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, if (isExtract) { unsigned char c = *p; +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + if (c == 0 || c == '.' || c == NAME_ESCAPE) + *cp++ = NAME_ESCAPE; + *cp++ = c; + } + else +#endif if (c != 0 && c != '.') *cp++ = c; else @@ -144,9 +153,14 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, cp++; if (c1 >= 'A' && c1 <= 'Z') c1 += 'a' - 'A'; +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && c1 == NAME_ESCAPE) + c1 = *cp++; +#endif + if (c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; - + if (c1 != c2) retvalue = 2; } diff --git a/src/util.c b/src/util.c index 648bc4d..0c1a48b 100644 --- a/src/util.c +++ b/src/util.c @@ -226,7 +226,14 @@ unsigned char *do_rfc1035_name(unsigned char *p, char *sval) { unsigned char *cp = p++; for (j = 0; *sval && (*sval != '.'); sval++, j++) - *p++ = *sval; + { +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE) + *p++ = *(++sval); + else +#endif + *p++ = *sval; + } *cp = j; if (*sval) sval++; -- cgit v1.2.1