Allow wildcards in domain patterns.

Domain patterns in --address, --server and --local have, for many years,
matched complete labels only, so
--server=/google.com/1.2.3.4
will apply to google.com and www.google.com but NOT supergoogle.com

This commit introduces an optional '*' at the LHS of the domain string which
changes this behaviour so as to include substring matches _within_ labels. So,
--server=/*google.com/1.2.3.4
applies to google.com, www.google.com AND supergoogle.com.
This commit is contained in:
Simon Kelley
2021-07-01 22:28:24 +01:00
parent 4205e2ebcf
commit 5e95c16c32
4 changed files with 84 additions and 27 deletions

View File

@@ -460,13 +460,22 @@ repeated domain or ipaddr parts as required.
More specific domains take precedence over less specific domains, so: More specific domains take precedence over less specific domains, so:
.B --server=/google.com/1.2.3.4 .B --server=/google.com/1.2.3.4
.B --server=/www.google.com/2.3.4.5 .B --server=/www.google.com/2.3.4.5
will send queries for *.google.com to 1.2.3.4, except *www.google.com, will send queries for google.com and gmail.google.com to 1.2.3.4, but www.google.com
which will go to 2.3.4.5 will go to 2.3.4.5
Matching of domains is normally done on complete labels, so /google.com/ matches google.com and www.google.com
but NOT supergoogle.com. This can be overridden with a * at the start of a pattern only: /*google.com/
will match google.com and www.google.com AND supergoogle.com. The non-wildcard form has priority, so
if /google.com/ and /*google.com/ are both specified then google.com and www.google.com will match /google.com/
and /*google.com/ will only match supergoogle.com.
For historical reasons, the pattern /.google.com/ is equivalent to /google.com/ if you wish to match any subdomain
of google.com but NOT google.com itself, use /*.google.com/
The special server address '#' means, "use the standard servers", so The special server address '#' means, "use the standard servers", so
.B --server=/google.com/1.2.3.4 .B --server=/google.com/1.2.3.4
.B --server=/www.google.com/# .B --server=/www.google.com/#
will send queries for *.google.com to 1.2.3.4, except *www.google.com which will will send queries for google.com and its subdomains to 1.2.3.4, except www.google.com (and its subdomains) which will
be forwarded as usual. be forwarded as usual.
Also permitted is a -S Also permitted is a -S

View File

@@ -537,7 +537,7 @@ union mysockaddr {
#define SERV_WARNED_RECURSIVE 64 /* avoid warning spam */ #define SERV_WARNED_RECURSIVE 64 /* avoid warning spam */
#define SERV_FROM_DBUS 128 /* 1 if source is DBus */ #define SERV_FROM_DBUS 128 /* 1 if source is DBus */
#define SERV_MARK 256 /* for mark-and-delete and log code */ #define SERV_MARK 256 /* for mark-and-delete and log code */
/* #define SERV_COUNTED 512 /* workspace for log code */ #define SERV_WILDCARD 512 /* domain has leading '*' */
#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */ #define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */
#define SERV_FROM_RESOLV 2048 /* 1 for servers from resolv, 0 for command line. */ #define SERV_FROM_RESOLV 2048 /* 1 for servers from resolv, 0 for command line. */
#define SERV_FROM_FILE 4096 /* read from --servers-file */ #define SERV_FROM_FILE 4096 /* read from --servers-file */
@@ -1103,6 +1103,7 @@ extern struct daemon {
struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces; struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
struct bogus_addr *bogus_addr, *ignore_addr; struct bogus_addr *bogus_addr, *ignore_addr;
struct server *servers, *local_domains, **serverarray, *no_rebind; struct server *servers, *local_domains, **serverarray, *no_rebind;
int server_has_wildcard;
int serverarraysz, serverarrayhwm; int serverarraysz, serverarrayhwm;
struct ipsets *ipsets; struct ipsets *ipsets;
u32 allowlist_mask; u32 allowlist_mask;

View File

@@ -32,10 +32,18 @@ void build_server_array(void)
#ifdef HAVE_LOOP #ifdef HAVE_LOOP
if (!(serv->flags & SERV_LOOP)) if (!(serv->flags & SERV_LOOP))
#endif #endif
{
count++; count++;
if (serv->flags & SERV_WILDCARD)
daemon->server_has_wildcard = 1;
}
for (serv = daemon->local_domains; serv; serv = serv->next) for (serv = daemon->local_domains; serv; serv = serv->next)
{
count++; count++;
if (serv->flags & SERV_WILDCARD)
daemon->server_has_wildcard = 1;
}
daemon->serverarraysz = count; daemon->serverarraysz = count;
@@ -92,13 +100,13 @@ void build_server_array(void)
reply of IPv4 or IPV6. reply of IPv4 or IPV6.
return 0 if nothing found, 1 otherwise. return 0 if nothing found, 1 otherwise.
*/ */
int lookup_domain(char *qdomain, int flags, int *lowout, int *highout) int lookup_domain(char *domain, int flags, int *lowout, int *highout)
{ {
int rc, crop_query, nodots; int rc, crop_query, nodots;
ssize_t qlen; ssize_t qlen;
int try, high, low = 0; int try, high, low = 0;
int nlow = 0, nhigh = 0; int nlow = 0, nhigh = 0;
char *cp; char *cp, *qdomain = domain;
/* may be no configured servers. */ /* may be no configured servers. */
if (daemon->serverarraysz == 0) if (daemon->serverarraysz == 0)
@@ -177,17 +185,37 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
}; };
if (rc == 0) if (rc == 0)
{
int found = 1;
if (daemon->server_has_wildcard)
{
/* if we have example.com and *example.com we need to check against *example.com,
but the binary search may have found either. Use the fact that example.com is sorted before *example.com
We favour example.com in the case that both match (ie www.example.com) */
while (try != 0 && order(qdomain, qlen, daemon->serverarray[try-1]) == 0)
try--;
if (!(qdomain == domain || *qdomain == 0 || *(qdomain-1) == '.'))
{
while (try < daemon->serverarraysz-1 && order(qdomain, qlen, daemon->serverarray[try+1]) == 0)
try++;
if (!(daemon->serverarray[try]->flags & SERV_WILDCARD))
found = 0;
}
}
if (found)
{ {
/* We've matched a setting which says to use servers without a domain. /* We've matched a setting which says to use servers without a domain.
Continue the search with empty query */ Continue the search with empty query */
if (daemon->serverarray[try]->flags & SERV_USE_RESOLV) if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
crop_query = qlen; crop_query = qlen;
else else if (filter_servers(try, flags, &nlow, &nhigh))
{
/* We have a match, but it may only be (say) an IPv6 address, and /* We have a match, but it may only be (say) an IPv6 address, and
if the query wasn't for an AAAA record, it's no good, and we need if the query wasn't for an AAAA record, it's no good, and we need
to continue generalising */ to continue generalising */
if (filter_servers(try, flags, &nlow, &nhigh))
break; break;
} }
} }
@@ -197,9 +225,11 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
crop_query = 1; crop_query = 1;
/* strip chars off the query based on the largest possible remaining match, /* strip chars off the query based on the largest possible remaining match,
then continue to the start of the next label. */ then continue to the start of the next label unless we have a wildcard
domain somewhere, in which case we have to go one at a time. */
qlen -= crop_query; qlen -= crop_query;
qdomain += crop_query; qdomain += crop_query;
if (!daemon->server_has_wildcard)
while (qlen > 0 && (*(qdomain-1) != '.')) while (qlen > 0 && (*(qdomain-1) != '.'))
qlen--, qdomain++; qlen--, qdomain++;
} }
@@ -449,13 +479,22 @@ static int order(char *qdomain, size_t qlen, struct server *serv)
static int order_servers(struct server *s1, struct server *s2) static int order_servers(struct server *s1, struct server *s2)
{ {
int rc;
/* need full comparison of dotless servers in /* need full comparison of dotless servers in
order_qsort() and filter_servers() */ order_qsort() and filter_servers() */
if (s1->flags & SERV_FOR_NODOTS) if (s1->flags & SERV_FOR_NODOTS)
return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1; return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
return order(s1->domain, s1->domain_len, s2); if ((rc = order(s1->domain, s1->domain_len, s2)) != 0)
return rc;
/* For identical domains, sort wildcard ones first */
if (s1->flags & SERV_WILDCARD)
return (s2->flags & SERV_WILDCARD) ? 0 : 1;
return (s2->flags & SERV_WILDCARD) ? -1 : 0;
} }
static int order_qsort(const void *a, const void *b) static int order_qsort(const void *a, const void *b)
@@ -540,8 +579,16 @@ int add_update_server(int flags,
if (!domain || strlen(domain) == 0) if (!domain || strlen(domain) == 0)
alloc_domain = whine_malloc(1); alloc_domain = whine_malloc(1);
else if (!(alloc_domain = canonicalise((char *)domain, NULL))) else
{
if (*domain == '*')
{
domain++;
flags |= SERV_WILDCARD;
}
if (!(alloc_domain = canonicalise((char *)domain, NULL)))
return 0; return 0;
}
/* See if there is a suitable candidate, and unmark /* See if there is a suitable candidate, and unmark
only do this for forwarding servers, not only do this for forwarding servers, not

View File

@@ -1588,7 +1588,7 @@ void check_servers(int no_loop_check)
if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS)) if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS))
{ {
char *s1, *s2, *s3 = ""; char *s1, *s2, *s3 = "", *s4 = "";
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC)) if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
@@ -1599,9 +1599,9 @@ void check_servers(int no_loop_check)
else if (strlen(serv->domain) == 0) else if (strlen(serv->domain) == 0)
s1 = _("default"), s2 = ""; s1 = _("default"), s2 = "";
else else
s1 = _("domain"), s2 = serv->domain; s1 = _("domain"), s2 = serv->domain, s4 = (serv->flags & SERV_WILDCARD) ? "*" : "";
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s %s"), daemon->namebuff, port, s1, s2, s3); my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s%s %s"), daemon->namebuff, port, s1, s4, s2, s3);
} }
#ifdef HAVE_LOOP #ifdef HAVE_LOOP
else if (serv->flags & SERV_LOOP) else if (serv->flags & SERV_LOOP)