diff --git a/CHANGELOG b/CHANGELOG index 5bcce38..34f3d56 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,10 @@ version 2.77 is destroyed and recreated in the kernel. Thanks to Beniamino Galvani for the patch. + Allow wildcard CNAME records in authoritative zones. + For example --cname=*.example.com,default.example.com + Thanks to Pro Backup for sponsoring this development. + version 2.76 Include 0.0.0.0/8 in DNS rebind checks. This range diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index a13e4f5..248bc83 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -2206,7 +2206,12 @@ following data is used to populate the authoritative zone. .B --cname as long as the record name is in the authoritative domain. If the target of the CNAME is unqualified, then it is qualified with the -authoritative zone name. +authoritative zone name. CNAME used in this way (only) may be wildcards, as in + +.nf +.B cname=*.example.com,default.example.com +.fi + .PP IPv4 and IPv6 addresses from /etc/hosts (and .B --addn-hosts diff --git a/src/auth.c b/src/auth.c index f1ca2f5..21c5f8c 100644 --- a/src/auth.c +++ b/src/auth.c @@ -116,7 +116,8 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n struct interface_name *intr; struct naptr *na; struct all_addr addr; - struct cname *a; + struct cname *a, *candidate; + unsigned int wclen; /* Clear buffer beyond request to avoid risk of information disclosure. */ @@ -137,6 +138,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n { unsigned short flag = 0; int found = 0; + int cname_wildcard = 0; /* save pointer to name for copying into answers */ nameoffset = p - (unsigned char *)header; @@ -411,25 +413,6 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } } - for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(name, a->alias) ) - { - log_query(F_CONFIG | F_CNAME, name, NULL, NULL); - strcpy(name, a->target); - if (!strchr(name, '.')) - { - strcat(name, "."); - strcat(name, zone->domain); - } - found = 1; - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->auth_ttl, &nameoffset, - T_CNAME, C_IN, "d", name)) - anscount++; - - goto cname_restart; - } - if (!cut) { nxdomain = 0; @@ -536,7 +519,60 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } if (!found) - log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); + { + /* Check for possible wildcard match against *.domain + return length of match, to get longest. + Note that if return length of wildcard section, so + we match b.simon to _both_ *.simon and b.simon + but return a longer (better) match to b.simon. + */ + for (wclen = 0, candidate = NULL, a = daemon->cnames; a; a = a->next) + if (a->alias[0] == '*') + { + char *test = name; + + while ((test = strchr(test+1, '.'))) + { + if (hostname_isequal(test, &(a->alias[1]))) + { + if (strlen(test) > wclen && !cname_wildcard) + { + wclen = strlen(test); + candidate = a; + cname_wildcard = 1; + } + break; + } + } + + } + else if (hostname_isequal(a->alias, name) && strlen(a->alias) > wclen) + { + /* Simple case, no wildcard */ + wclen = strlen(a->alias); + candidate = a; + } + + if (candidate) + { + log_query(F_CONFIG | F_CNAME, name, NULL, NULL); + strcpy(name, candidate->target); + if (!strchr(name, '.')) + { + strcat(name, "."); + strcat(name, zone->domain); + } + found = 1; + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, &nameoffset, + T_CNAME, C_IN, "d", name)) + anscount++; + + goto cname_restart; + } + + log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); + } } diff --git a/src/cache.c b/src/cache.c index 4ecd535..b031fec 100644 --- a/src/cache.c +++ b/src/cache.c @@ -774,7 +774,8 @@ static void add_hosts_cname(struct crec *target) struct cname *a; for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(cache_get_name(target), a->target) && + if (a->alias[1] != '*' && + hostname_isequal(cache_get_name(target), a->target) && (crec = whine_malloc(sizeof(struct crec)))) { crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME; @@ -1056,7 +1057,8 @@ void cache_reload(void) /* Add CNAMEs to interface_names to the cache */ for (a = daemon->cnames; a; a = a->next) for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(a->target, intr->name) && + if (a->alias[1] != '*' && + hostname_isequal(a->target, intr->name) && ((cache = whine_malloc(sizeof(struct crec))))) { cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; @@ -1177,7 +1179,8 @@ static void add_dhcp_cname(struct crec *target, time_t ttd) struct cname *a; for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(cache_get_name(target), a->target)) + if (a->alias[1] != '*' && + hostname_isequal(cache_get_name(target), a->target)) { if ((aliasc = dhcp_spare)) dhcp_spare = dhcp_spare->next; diff --git a/src/util.c b/src/util.c index 211690e..c99eb0d 100644 --- a/src/util.c +++ b/src/util.c @@ -323,7 +323,7 @@ int hostname_isequal(const char *a, const char *b) return 1; } - + time_t dnsmasq_time(void) { #ifdef HAVE_BROKEN_RTC