mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 02:08:24 +00:00
servers for plain names (server=//1.2.3.4) as domain-specific.
This means that if we fail to get a DS record for such a query,
it gets given the benefit of the doubt.
Updates 57f0489f38
767 lines
21 KiB
C
767 lines
21 KiB
C
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 dated June, 1991, or
|
|
(at your option) version 3 dated 29 June, 2007.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "dnsmasq.h"
|
|
|
|
static int order(char *qdomain, size_t qlen, struct server *serv);
|
|
static int order_qsort(const void *a, const void *b);
|
|
static int order_servers(struct server *s, struct server *s2);
|
|
|
|
/* If the server is USE_RESOLV or LITERAL_ADDRESS, it lives on the local_domains chain. */
|
|
#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
|
|
|
|
void build_server_array(void)
|
|
{
|
|
struct server *serv;
|
|
int count = 0;
|
|
|
|
for (serv = daemon->servers; serv; serv = serv->next)
|
|
#ifdef HAVE_LOOP
|
|
if (!(serv->flags & SERV_LOOP))
|
|
#endif
|
|
{
|
|
count++;
|
|
if (serv->flags & SERV_WILDCARD)
|
|
daemon->server_has_wildcard = 1;
|
|
}
|
|
|
|
for (serv = daemon->local_domains; serv; serv = serv->next)
|
|
{
|
|
count++;
|
|
if (serv->flags & SERV_WILDCARD)
|
|
daemon->server_has_wildcard = 1;
|
|
}
|
|
|
|
daemon->serverarraysz = count;
|
|
|
|
if (count > daemon->serverarrayhwm)
|
|
{
|
|
struct server **new;
|
|
|
|
count += 10; /* A few extra without re-allocating. */
|
|
|
|
if ((new = whine_malloc(count * sizeof(struct server *))))
|
|
{
|
|
if (daemon->serverarray)
|
|
free(daemon->serverarray);
|
|
|
|
daemon->serverarray = new;
|
|
daemon->serverarrayhwm = count;
|
|
}
|
|
}
|
|
|
|
count = 0;
|
|
|
|
for (serv = daemon->servers; serv; serv = serv->next)
|
|
#ifdef HAVE_LOOP
|
|
if (!(serv->flags & SERV_LOOP))
|
|
#endif
|
|
{
|
|
daemon->serverarray[count] = serv;
|
|
serv->serial = count;
|
|
serv->last_server = -1;
|
|
count++;
|
|
}
|
|
|
|
for (serv = daemon->local_domains; serv; serv = serv->next, count++)
|
|
daemon->serverarray[count] = serv;
|
|
|
|
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
|
|
|
|
/* servers need the location in the array to find all the whole
|
|
set of equivalent servers from a pointer to a single one. */
|
|
for (count = 0; count < daemon->serverarraysz; count++)
|
|
if (!(daemon->serverarray[count]->flags & SERV_IS_LOCAL))
|
|
daemon->serverarray[count]->arrayposn = count;
|
|
}
|
|
|
|
/* we're looking for the server whose domain is the longest exact match
|
|
to the RH end of qdomain, or a local address if the flags match.
|
|
Add '.' to the LHS of the query string so
|
|
server=/.example.com/ works.
|
|
|
|
A flag of F_SERVER returns an upstream server only.
|
|
A flag of F_DNSSECOK disables NODOTS servers from consideration.
|
|
A flag of F_DOMAINSRV returns a domain-specific server only.
|
|
A flag of F_CONFIG returns anything that generates a local
|
|
reply of IPv4 or IPV6.
|
|
return 0 if nothing found, 1 otherwise.
|
|
*/
|
|
int lookup_domain(char *domain, int flags, int *lowout, int *highout)
|
|
{
|
|
int rc, crop_query, nodots;
|
|
ssize_t qlen;
|
|
int try, high, low = 0;
|
|
int nlow = 0, nhigh = 0;
|
|
char *cp, *qdomain = domain;
|
|
|
|
/* may be no configured servers. */
|
|
if (daemon->serverarraysz == 0)
|
|
return 0;
|
|
|
|
/* find query length and presence of '.' */
|
|
for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
|
|
if (*cp == '.')
|
|
nodots = 0;
|
|
|
|
/* Handle empty name, and searches for DNSSEC queries without
|
|
diverting to NODOTS servers. */
|
|
if (qlen == 0 || flags & F_DNSSECOK)
|
|
nodots = 0;
|
|
|
|
/* Search shorter and shorter RHS substrings for a match */
|
|
while (qlen >= 0)
|
|
{
|
|
/* Note that when we chop off a label, all the possible matches
|
|
MUST be at a larger index than the nearest failing match with one more
|
|
character, since the array is sorted longest to smallest. Hence
|
|
we don't reset low to zero here, we can go further below and crop the
|
|
search string to the size of the largest remaining server
|
|
when this match fails. */
|
|
high = daemon->serverarraysz;
|
|
crop_query = 1;
|
|
|
|
/* binary search */
|
|
while (1)
|
|
{
|
|
try = (low + high)/2;
|
|
|
|
if ((rc = order(qdomain, qlen, daemon->serverarray[try])) == 0)
|
|
break;
|
|
|
|
if (rc < 0)
|
|
{
|
|
if (high == try)
|
|
{
|
|
/* qdomain is longer or same length as longest domain, and try == 0
|
|
crop the query to the longest domain. */
|
|
crop_query = qlen - daemon->serverarray[try]->domain_len;
|
|
break;
|
|
}
|
|
high = try;
|
|
}
|
|
else
|
|
{
|
|
if (low == try)
|
|
{
|
|
/* try now points to the last domain that sorts before the query, so
|
|
we know that a substring of the query shorter than it is required to match, so
|
|
find the largest domain that's shorter than try. Note that just going to
|
|
try+1 is not optimal, consider searching bbb in (aaa,ccc,bb). try will point
|
|
to aaa, since ccc sorts after bbb, but the first domain that has a chance to
|
|
match is bb. So find the length of the first domain later than try which is
|
|
is shorter than it.
|
|
There's a nasty edge case when qdomain sorts before _any_ of the
|
|
server domains, where try _doesn't point_ to the last domain that sorts
|
|
before the query, since no such domain exists. In that case, the loop
|
|
exits via the rc < 0 && high == try path above and this code is
|
|
not executed. */
|
|
ssize_t len, old = daemon->serverarray[try]->domain_len;
|
|
while (++try != daemon->serverarraysz)
|
|
{
|
|
if (old != (len = daemon->serverarray[try]->domain_len))
|
|
{
|
|
crop_query = qlen - len;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
low = try;
|
|
}
|
|
};
|
|
|
|
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 && filter_servers(try, flags, &nlow, &nhigh))
|
|
/* 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
|
|
to continue generalising */
|
|
{
|
|
/* We've matched a setting which says to use servers without a domain.
|
|
Continue the search with empty query. We set the F_SERVER flag
|
|
so that --address=/#/... doesn't match. */
|
|
if (daemon->serverarray[nlow]->flags & SERV_USE_RESOLV)
|
|
{
|
|
crop_query = qlen;
|
|
flags |= F_SERVER;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* crop_query must be at least one always. */
|
|
if (crop_query == 0)
|
|
crop_query = 1;
|
|
|
|
/* strip chars off the query based on the largest possible remaining match,
|
|
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;
|
|
qdomain += crop_query;
|
|
if (!daemon->server_has_wildcard)
|
|
while (qlen > 0 && (*(qdomain-1) != '.'))
|
|
qlen--, qdomain++;
|
|
}
|
|
|
|
/* domain has no dots, and we have at least one server configured to handle such,
|
|
These servers always sort to the very end of the array.
|
|
A configured server eg server=/lan/ will take precdence. */
|
|
if (nodots &&
|
|
(daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
|
|
(nlow == nhigh || daemon->serverarray[nlow]->domain_len == 0))
|
|
{
|
|
filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
|
|
qlen = 0;
|
|
}
|
|
|
|
if (lowout)
|
|
*lowout = nlow;
|
|
|
|
if (highout)
|
|
*highout = nhigh;
|
|
|
|
/* qlen == -1 when we failed to match even an empty query, if there are no default servers. */
|
|
if (nlow == nhigh || qlen == -1)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int server_samegroup(struct server *a, struct server *b)
|
|
{
|
|
return order_servers(a, b) == 0;
|
|
}
|
|
|
|
int filter_servers(int seed, int flags, int *lowout, int *highout)
|
|
{
|
|
int nlow = seed, nhigh = seed;
|
|
int i;
|
|
|
|
/* expand nlow and nhigh to cover all the records with the same domain
|
|
nlow is the first, nhigh - 1 is the last. nlow=nhigh means no servers,
|
|
which can happen below. */
|
|
while (nlow > 0 && order_servers(daemon->serverarray[nlow-1], daemon->serverarray[nlow]) == 0)
|
|
nlow--;
|
|
|
|
while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0)
|
|
nhigh++;
|
|
|
|
nhigh++;
|
|
|
|
#define SERV_LOCAL_ADDRESS (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)
|
|
|
|
if (flags & F_CONFIG)
|
|
{
|
|
/* We're just lookin for any matches that return an RR. */
|
|
for (i = nlow; i < nhigh; i++)
|
|
if (daemon->serverarray[i]->flags & SERV_LOCAL_ADDRESS)
|
|
break;
|
|
|
|
/* failed, return failure. */
|
|
if (i == nhigh)
|
|
nhigh = nlow;
|
|
}
|
|
else
|
|
{
|
|
/* Now the matching server records are all between low and high.
|
|
order_qsort() ensures that they are in the order
|
|
IPv6 addr, IPv4 addr, return zero for both, no-data return,
|
|
"use resolvconf" servers, domain-specific upstream servers.
|
|
|
|
See which of those match our query in that priority order and narrow (low, high) */
|
|
|
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
|
|
|
|
if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV6))
|
|
nhigh = i;
|
|
else
|
|
{
|
|
nlow = i;
|
|
|
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
|
|
|
|
if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV4))
|
|
nhigh = i;
|
|
else
|
|
{
|
|
nlow = i;
|
|
|
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
|
|
|
|
if (!(flags & F_SERVER) && i != nlow && (flags & (F_IPV4 | F_IPV6)))
|
|
nhigh = i;
|
|
else
|
|
{
|
|
nlow = i;
|
|
|
|
/* now look for a NXDOMAIN answer --local=/domain/ */
|
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
|
|
|
|
if (!(flags & (F_DOMAINSRV | F_SERVER)) && i != nlow)
|
|
nhigh = i;
|
|
else
|
|
{
|
|
nlow = i;
|
|
|
|
/* return "use resolv.conf servers" if they exist */
|
|
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
|
|
|
|
if (i != nlow)
|
|
nhigh = i;
|
|
else
|
|
{
|
|
/* If we want a server for a particular domain, and this one isn't, return nothing. */
|
|
if (nlow < daemon->serverarraysz && nlow != nhigh && (flags & F_DOMAINSRV) &&
|
|
daemon->serverarray[nlow]->domain_len == 0 && !(daemon->serverarray[nlow]->flags & SERV_FOR_NODOTS))
|
|
nlow = nhigh;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*lowout = nlow;
|
|
*highout = nhigh;
|
|
|
|
return (nlow != nhigh);
|
|
}
|
|
|
|
int is_local_answer(time_t now, int first, char *name)
|
|
{
|
|
int flags = 0;
|
|
int rc = 0;
|
|
|
|
if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
|
|
{
|
|
if (flags & SERV_4ADDR)
|
|
rc = F_IPV4;
|
|
else if (flags & SERV_6ADDR)
|
|
rc = F_IPV6;
|
|
else if (flags & SERV_ALL_ZEROS)
|
|
rc = F_IPV4 | F_IPV6;
|
|
else
|
|
{
|
|
/* argument first is the first struct server which matches the query type;
|
|
now roll back to the server which is just the same domain, to check if that
|
|
provides an answer of a different type. */
|
|
|
|
for (;first > 0 && order_servers(daemon->serverarray[first-1], daemon->serverarray[first]) == 0; first--);
|
|
|
|
if ((daemon->serverarray[first]->flags & SERV_LOCAL_ADDRESS) ||
|
|
check_for_local_domain(name, now))
|
|
rc = F_NOERR;
|
|
else
|
|
rc = F_NXDOMAIN;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede)
|
|
{
|
|
int trunc = 0, anscount = 0;
|
|
unsigned char *p;
|
|
int start;
|
|
union all_addr addr;
|
|
|
|
setup_reply(header, flags, ede);
|
|
|
|
gotname &= ~F_QUERY;
|
|
|
|
if (flags & (F_NXDOMAIN | F_NOERR))
|
|
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL, 0);
|
|
|
|
if (flags & F_RCODE)
|
|
{
|
|
union all_addr a;
|
|
a.log.rcode = RCODE(header);
|
|
a.log.ede = ede;
|
|
log_query(F_UPSTREAM | F_RCODE, "opcode", &a, NULL, 0);
|
|
}
|
|
|
|
if (!(p = skip_questions(header, size)))
|
|
return 0;
|
|
|
|
if (flags & gotname & F_IPV4)
|
|
for (start = first; start != last; start++)
|
|
{
|
|
struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start];
|
|
|
|
if (srv->flags & SERV_ALL_ZEROS)
|
|
memset(&addr, 0, sizeof(addr));
|
|
else
|
|
addr.addr4 = srv->addr;
|
|
|
|
if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr))
|
|
anscount++;
|
|
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL, 0);
|
|
}
|
|
|
|
if (flags & gotname & F_IPV6)
|
|
for (start = first; start != last; start++)
|
|
{
|
|
struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start];
|
|
|
|
if (srv->flags & SERV_ALL_ZEROS)
|
|
memset(&addr, 0, sizeof(addr));
|
|
else
|
|
addr.addr6 = srv->addr;
|
|
|
|
if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr))
|
|
anscount++;
|
|
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL, 0);
|
|
}
|
|
|
|
if (trunc)
|
|
{
|
|
header->hb3 |= HB3_TC;
|
|
if (!(p = skip_questions(header, size)))
|
|
return 0; /* bad packet */
|
|
anscount = 0;
|
|
}
|
|
|
|
header->ancount = htons(anscount);
|
|
|
|
return p - (unsigned char *)header;
|
|
}
|
|
|
|
#ifdef HAVE_DNSSEC
|
|
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
|
|
{
|
|
int first, last, index;
|
|
|
|
/* Find server to send DNSSEC query to. This will normally be the
|
|
same as for the original query, but may be another if
|
|
servers for domains are involved. */
|
|
if (!lookup_domain(keyname, F_SERVER | F_DNSSECOK, &first, &last))
|
|
return -1;
|
|
|
|
for (index = first; index != last; index++)
|
|
if (daemon->serverarray[index] == server)
|
|
break;
|
|
|
|
/* No match to server used for original query.
|
|
Use newly looked up set. */
|
|
if (index == last)
|
|
index = daemon->serverarray[first]->last_server == -1 ?
|
|
first : daemon->serverarray[first]->last_server;
|
|
|
|
if (firstp)
|
|
*firstp = first;
|
|
|
|
if (lastp)
|
|
*lastp = last;
|
|
|
|
return index;
|
|
}
|
|
#endif
|
|
|
|
/* order by size, then by dictionary order */
|
|
static int order(char *qdomain, size_t qlen, struct server *serv)
|
|
{
|
|
size_t dlen = 0;
|
|
|
|
/* servers for dotless names always sort last
|
|
searched for name is never dotless. */
|
|
if (serv->flags & SERV_FOR_NODOTS)
|
|
return -1;
|
|
|
|
dlen = serv->domain_len;
|
|
|
|
if (qlen < dlen)
|
|
return 1;
|
|
|
|
if (qlen > dlen)
|
|
return -1;
|
|
|
|
return hostname_order(qdomain, serv->domain);
|
|
}
|
|
|
|
static int order_servers(struct server *s1, struct server *s2)
|
|
{
|
|
int rc;
|
|
|
|
/* need full comparison of dotless servers in
|
|
order_qsort() and filter_servers() */
|
|
|
|
if (s1->flags & SERV_FOR_NODOTS)
|
|
return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
|
|
|
|
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)
|
|
{
|
|
int rc;
|
|
|
|
struct server *s1 = *((struct server **)a);
|
|
struct server *s2 = *((struct server **)b);
|
|
|
|
rc = order_servers(s1, s2);
|
|
|
|
/* Sort all literal NODATA and local IPV4 or IPV6 responses together,
|
|
in a very specific order IPv6 literal, IPv4 literal, all-zero literal,
|
|
NXDOMAIN literal. We also include SERV_USE_RESOLV in this, so that
|
|
use-standard servers sort before ordinary servers. (SERV_USR_RESOLV set
|
|
implies that none of SERV_LITERAL_ADDRESS,SERV_4ADDR,SERV_6ADDR,SERV_ALL_ZEROS
|
|
are set) */
|
|
if (rc == 0)
|
|
rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS | SERV_USE_RESOLV))) -
|
|
((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS | SERV_USE_RESOLV)));
|
|
|
|
/* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
|
|
if (rc == 0)
|
|
if (!(s1->flags & SERV_IS_LOCAL) && !(s2->flags & SERV_IS_LOCAL))
|
|
rc = s1->serial - s2->serial;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/* When loading large numbers of server=.... lines during startup,
|
|
there's no possibility that there will be server records that can be reused, but
|
|
searching a long list for each server added grows as O(n^2) and slows things down.
|
|
This flag is set only if is known there may be free server records that can be reused.
|
|
There's a call to mark_servers(0) in read_opts() to reset the flag before
|
|
main config read. */
|
|
|
|
static int maybe_free_servers = 0;
|
|
|
|
/* Must be called before add_update_server() to set daemon->servers_tail */
|
|
void mark_servers(int flag)
|
|
{
|
|
struct server *serv, *next, **up;
|
|
|
|
maybe_free_servers = !!flag;
|
|
|
|
daemon->servers_tail = NULL;
|
|
|
|
/* mark everything with argument flag */
|
|
for (serv = daemon->servers; serv; serv = serv->next)
|
|
{
|
|
if (serv->flags & flag)
|
|
serv->flags |= SERV_MARK;
|
|
else
|
|
serv->flags &= ~SERV_MARK;
|
|
|
|
daemon->servers_tail = serv;
|
|
}
|
|
|
|
/* --address etc is different: since they are expected to be
|
|
1) numerous and 2) not reloaded often. We just delete
|
|
and recreate. */
|
|
if (flag)
|
|
for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = next)
|
|
{
|
|
next = serv->next;
|
|
|
|
if (serv->flags & flag)
|
|
{
|
|
*up = next;
|
|
free(serv->domain);
|
|
free(serv);
|
|
}
|
|
else
|
|
up = &serv->next;
|
|
}
|
|
}
|
|
|
|
void cleanup_servers(void)
|
|
{
|
|
struct server *serv, *tmp, **up;
|
|
|
|
/* unlink and free anything still marked. */
|
|
for (serv = daemon->servers, up = &daemon->servers, daemon->servers_tail = NULL; serv; serv = tmp)
|
|
{
|
|
tmp = serv->next;
|
|
if (serv->flags & SERV_MARK)
|
|
{
|
|
server_gone(serv);
|
|
*up = serv->next;
|
|
free(serv->domain);
|
|
free(serv);
|
|
}
|
|
else
|
|
{
|
|
up = &serv->next;
|
|
daemon->servers_tail = serv;
|
|
}
|
|
}
|
|
}
|
|
|
|
int add_update_server(int flags,
|
|
union mysockaddr *addr,
|
|
union mysockaddr *source_addr,
|
|
const char *interface,
|
|
const char *domain,
|
|
union all_addr *local_addr)
|
|
{
|
|
struct server *serv = NULL;
|
|
char *alloc_domain;
|
|
|
|
if (!domain)
|
|
domain = "";
|
|
|
|
/* .domain == domain, for historical reasons. */
|
|
if (*domain == '.')
|
|
while (*domain == '.') domain++;
|
|
else if (*domain == '*')
|
|
{
|
|
domain++;
|
|
if (*domain != 0)
|
|
flags |= SERV_WILDCARD;
|
|
}
|
|
|
|
if (*domain == 0)
|
|
alloc_domain = whine_malloc(1);
|
|
else
|
|
alloc_domain = canonicalise((char *)domain, NULL);
|
|
|
|
if (!alloc_domain)
|
|
return 0;
|
|
|
|
if (flags & SERV_IS_LOCAL)
|
|
{
|
|
size_t size;
|
|
|
|
if (flags & SERV_6ADDR)
|
|
size = sizeof(struct serv_addr6);
|
|
else if (flags & SERV_4ADDR)
|
|
size = sizeof(struct serv_addr4);
|
|
else
|
|
size = sizeof(struct serv_local);
|
|
|
|
if (!(serv = whine_malloc(size)))
|
|
{
|
|
free(alloc_domain);
|
|
return 0;
|
|
}
|
|
|
|
serv->next = daemon->local_domains;
|
|
daemon->local_domains = serv;
|
|
|
|
if (flags & SERV_4ADDR)
|
|
((struct serv_addr4*)serv)->addr = local_addr->addr4;
|
|
|
|
if (flags & SERV_6ADDR)
|
|
((struct serv_addr6*)serv)->addr = local_addr->addr6;
|
|
}
|
|
else
|
|
{
|
|
/* Upstream servers. See if there is a suitable candidate, if so unmark
|
|
and move to the end of the list, for order. The entry found may already
|
|
be at the end. */
|
|
struct server **up, *tmp;
|
|
|
|
serv = NULL;
|
|
|
|
if (maybe_free_servers)
|
|
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
|
|
{
|
|
tmp = serv->next;
|
|
if ((serv->flags & SERV_MARK) &&
|
|
hostname_isequal(alloc_domain, serv->domain))
|
|
{
|
|
/* Need to move down? */
|
|
if (serv->next)
|
|
{
|
|
*up = serv->next;
|
|
daemon->servers_tail->next = serv;
|
|
daemon->servers_tail = serv;
|
|
serv->next = NULL;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
up = &serv->next;
|
|
}
|
|
|
|
if (serv)
|
|
{
|
|
free(alloc_domain);
|
|
alloc_domain = serv->domain;
|
|
}
|
|
else
|
|
{
|
|
if (!(serv = whine_malloc(sizeof(struct server))))
|
|
{
|
|
free(alloc_domain);
|
|
return 0;
|
|
}
|
|
|
|
memset(serv, 0, sizeof(struct server));
|
|
|
|
/* Add to the end of the chain, for order */
|
|
if (daemon->servers_tail)
|
|
daemon->servers_tail->next = serv;
|
|
else
|
|
daemon->servers = serv;
|
|
daemon->servers_tail = serv;
|
|
}
|
|
|
|
#ifdef HAVE_LOOP
|
|
serv->uid = rand32();
|
|
#endif
|
|
|
|
if (interface)
|
|
safe_strncpy(serv->interface, interface, sizeof(serv->interface));
|
|
if (addr)
|
|
serv->addr = *addr;
|
|
if (source_addr)
|
|
serv->source_addr = *source_addr;
|
|
|
|
serv->tcpfd = -1;
|
|
}
|
|
|
|
serv->flags = flags;
|
|
serv->domain = alloc_domain;
|
|
serv->domain_len = strlen(alloc_domain);
|
|
|
|
return 1;
|
|
}
|
|
|