/* dnsmasq is Copyright (c) 2000-2009 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 struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL;
|
#ifdef HAVE_DHCP
|
static struct crec* dhcp_spare = NULL;
|
#endif
|
static struct crec* new_chain = NULL;
|
static int cache_inserted = 0, cache_live_freed = 0, insert_error;
|
static union bigname* big_free = NULL;
|
static int bignames_left, hash_size;
|
static int uid = 0;
|
static char* addrbuff = NULL;
|
|
/* type->string mapping: this is also used by the name-hash function as a mixing table. */
|
static const struct {
|
unsigned int type;
|
const char* const name;
|
} typestr[] = {{1, "A"}, {2, "NS"}, {5, "CNAME"}, {6, "SOA"}, {10, "NULL"},
|
{11, "WKS"}, {12, "PTR"}, {13, "HINFO"}, {15, "MX"}, {16, "TXT"},
|
{22, "NSAP"}, {23, "NSAP_PTR"}, {24, "SIG"}, {25, "KEY"}, {28, "AAAA"},
|
{33, "SRV"}, {35, "NAPTR"}, {36, "KX"}, {37, "CERT"}, {38, "A6"},
|
{39, "DNAME"}, {41, "OPT"}, {48, "DNSKEY"}, {249, "TKEY"}, {250, "TSIG"},
|
{251, "IXFR"}, {252, "AXFR"}, {253, "MAILB"}, {254, "MAILA"}, {255, "ANY"}};
|
|
static void cache_free(struct crec* crecp);
|
static void cache_unlink(struct crec* crecp);
|
static void cache_link(struct crec* crecp);
|
static void rehash(int size);
|
static void cache_hash(struct crec* crecp);
|
|
void cache_init(void) {
|
struct crec* crecp;
|
int i;
|
|
if (daemon->options & OPT_LOG) addrbuff = safe_malloc(ADDRSTRLEN);
|
|
bignames_left = daemon->cachesize / 10;
|
|
if (daemon->cachesize > 0) {
|
crecp = safe_malloc(daemon->cachesize * sizeof(struct crec));
|
|
for (i = 0; i < daemon->cachesize; i++, crecp++) {
|
cache_link(crecp);
|
crecp->flags = 0;
|
crecp->uid = uid++;
|
}
|
}
|
|
/* create initial hash table*/
|
rehash(daemon->cachesize);
|
}
|
|
/* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
|
but if the hosts file(s) are big (some people have 50000 ad-block entries), the table
|
will be much too small, so the hosts reading code calls rehash every 1000 addresses, to
|
expand the table. */
|
static void rehash(int size) {
|
struct crec** new, **old, *p, *tmp;
|
int i, new_size, old_size;
|
|
/* hash_size is a power of two. */
|
for (new_size = 64; new_size < size / 10; new_size = new_size << 1)
|
;
|
|
/* must succeed in getting first instance, failure later is non-fatal */
|
if (!hash_table)
|
new = safe_malloc(new_size * sizeof(struct crec*));
|
else if (new_size <= hash_size || !(new = whine_malloc(new_size * sizeof(struct crec*))))
|
return;
|
|
for (i = 0; i < new_size; i++) new[i] = NULL;
|
|
old = hash_table;
|
old_size = hash_size;
|
hash_table = new;
|
hash_size = new_size;
|
|
if (old) {
|
for (i = 0; i < old_size; i++)
|
for (p = old[i]; p; p = tmp) {
|
tmp = p->hash_next;
|
cache_hash(p);
|
}
|
free(old);
|
}
|
}
|
|
static struct crec** hash_bucket(char* name) {
|
unsigned int c, val = 017465; /* Barker code - minimum self-correlation in cyclic shift */
|
const unsigned char* mix_tab = (const unsigned char*) typestr;
|
|
while ((c = (unsigned char) *name++)) {
|
/* don't use tolower and friends here - they may be messed up by LOCALE */
|
if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
|
val = ((val << 7) | (val >> (32 - 7))) + (mix_tab[(val + c) & 0x3F] ^ c);
|
}
|
|
/* hash_size is a power of two */
|
return hash_table + ((val ^ (val >> 16)) & (hash_size - 1));
|
}
|
|
static void cache_hash(struct crec* crecp) {
|
/* maintain an invariant that all entries with F_REVERSE set
|
are at the start of the hash-chain and all non-reverse
|
immortal entries are at the end of the hash-chain.
|
This allows reverse searches and garbage collection to be optimised */
|
|
struct crec** up = hash_bucket(cache_get_name(crecp));
|
|
if (!(crecp->flags & F_REVERSE)) {
|
while (*up && ((*up)->flags & F_REVERSE)) up = &((*up)->hash_next);
|
|
if (crecp->flags & F_IMMORTAL)
|
while (*up && !((*up)->flags & F_IMMORTAL)) up = &((*up)->hash_next);
|
}
|
crecp->hash_next = *up;
|
*up = crecp;
|
}
|
|
static void cache_free(struct crec* crecp) {
|
crecp->flags &= ~F_FORWARD;
|
crecp->flags &= ~F_REVERSE;
|
crecp->uid = uid++; /* invalidate CNAMES pointing to this. */
|
|
if (cache_tail)
|
cache_tail->next = crecp;
|
else
|
cache_head = crecp;
|
crecp->prev = cache_tail;
|
crecp->next = NULL;
|
cache_tail = crecp;
|
|
/* retrieve big name for further use. */
|
if (crecp->flags & F_BIGNAME) {
|
crecp->name.bname->next = big_free;
|
big_free = crecp->name.bname;
|
crecp->flags &= ~F_BIGNAME;
|
}
|
}
|
|
/* insert a new cache entry at the head of the list (youngest entry) */
|
static void cache_link(struct crec* crecp) {
|
if (cache_head) /* check needed for init code */
|
cache_head->prev = crecp;
|
crecp->next = cache_head;
|
crecp->prev = NULL;
|
cache_head = crecp;
|
if (!cache_tail) cache_tail = crecp;
|
}
|
|
/* remove an arbitrary cache entry for promotion */
|
static void cache_unlink(struct crec* crecp) {
|
if (crecp->prev)
|
crecp->prev->next = crecp->next;
|
else
|
cache_head = crecp->next;
|
|
if (crecp->next)
|
crecp->next->prev = crecp->prev;
|
else
|
cache_tail = crecp->prev;
|
}
|
|
char* cache_get_name(struct crec* crecp) {
|
if (crecp->flags & F_BIGNAME)
|
return crecp->name.bname->name;
|
else if (crecp->flags & (F_DHCP | F_CONFIG))
|
return crecp->name.namep;
|
|
return crecp->name.sname;
|
}
|
|
static int is_outdated_cname_pointer(struct crec* crecp) {
|
if (!(crecp->flags & F_CNAME)) return 0;
|
|
if (crecp->addr.cname.cache && crecp->addr.cname.uid == crecp->addr.cname.cache->uid) return 0;
|
|
return 1;
|
}
|
|
static int is_expired(time_t now, struct crec* crecp) {
|
if (crecp->flags & F_IMMORTAL) return 0;
|
|
if (difftime(now, crecp->ttd) < 0) return 0;
|
|
return 1;
|
}
|
|
static int cache_scan_free(char* name, struct all_addr* addr, time_t now, unsigned short flags) {
|
/* Scan and remove old entries.
|
If (flags & F_FORWARD) then remove any forward entries for name and any expired
|
entries but only in the same hash bucket as name.
|
If (flags & F_REVERSE) then remove any reverse entries for addr and any expired
|
entries in the whole cache.
|
If (flags == 0) remove any expired entries in the whole cache.
|
|
In the flags & F_FORWARD case, the return code is valid, and returns zero if the
|
name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
|
|
We take advantage of the fact that hash chains have stuff in the order
|
<reverse>,<other>,<immortal> so that when we hit an entry which isn't reverse and is
|
immortal, we're done. */
|
|
struct crec *crecp, **up;
|
|
if (flags & F_FORWARD) {
|
for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
|
if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) {
|
*up = crecp->hash_next;
|
if (!(crecp->flags & (F_HOSTS | F_DHCP))) {
|
cache_unlink(crecp);
|
cache_free(crecp);
|
}
|
} else if ((crecp->flags & F_FORWARD) &&
|
((flags & crecp->flags & (F_IPV4 | F_IPV6)) ||
|
((crecp->flags | flags) & F_CNAME)) &&
|
hostname_isequal(cache_get_name(crecp), name)) {
|
if (crecp->flags & (F_HOSTS | F_DHCP)) return 0;
|
*up = crecp->hash_next;
|
cache_unlink(crecp);
|
cache_free(crecp);
|
} else
|
up = &crecp->hash_next;
|
} else {
|
int i;
|
#ifdef HAVE_IPV6
|
int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ;
|
#else
|
int addrlen = INADDRSZ;
|
#endif
|
for (i = 0; i < hash_size; i++)
|
for (crecp = hash_table[i], up = &hash_table[i];
|
crecp && ((crecp->flags & F_REVERSE) || !(crecp->flags & F_IMMORTAL));
|
crecp = crecp->hash_next)
|
if (is_expired(now, crecp)) {
|
*up = crecp->hash_next;
|
if (!(crecp->flags & (F_HOSTS | F_DHCP))) {
|
cache_unlink(crecp);
|
cache_free(crecp);
|
}
|
} else if (!(crecp->flags & (F_HOSTS | F_DHCP)) &&
|
(flags & crecp->flags & F_REVERSE) &&
|
(flags & crecp->flags & (F_IPV4 | F_IPV6)) &&
|
memcmp(&crecp->addr.addr, addr, addrlen) == 0) {
|
*up = crecp->hash_next;
|
cache_unlink(crecp);
|
cache_free(crecp);
|
} else
|
up = &crecp->hash_next;
|
}
|
|
return 1;
|
}
|
|
/* Note: The normal calling sequence is
|
cache_start_insert
|
cache_insert * n
|
cache_end_insert
|
|
but an abort can cause the cache_end_insert to be missed
|
in which can the next cache_start_insert cleans things up. */
|
|
void cache_start_insert(void) {
|
/* Free any entries which didn't get committed during the last
|
insert due to error.
|
*/
|
while (new_chain) {
|
struct crec* tmp = new_chain->next;
|
cache_free(new_chain);
|
new_chain = tmp;
|
}
|
new_chain = NULL;
|
insert_error = 0;
|
}
|
|
struct crec* cache_insert(char* name, struct all_addr* addr, time_t now, unsigned long ttl,
|
unsigned short flags) {
|
struct crec* new;
|
union bigname* big_name = NULL;
|
int freed_all = flags & F_REVERSE;
|
int free_avail = 0;
|
|
log_query(flags | F_UPSTREAM, name, addr, NULL);
|
|
/* CONFIG bit means something else when stored in cache entries */
|
flags &= ~F_CONFIG;
|
|
/* if previous insertion failed give up now. */
|
if (insert_error) return NULL;
|
|
/* First remove any expired entries and entries for the name/address we
|
are currently inserting. Fail is we attempt to delete a name from
|
/etc/hosts or DHCP. */
|
if (!cache_scan_free(name, addr, now, flags)) {
|
insert_error = 1;
|
return NULL;
|
}
|
|
/* Now get a cache entry from the end of the LRU list */
|
while (1) {
|
if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
|
{
|
insert_error = 1;
|
return NULL;
|
}
|
|
/* End of LRU list is still in use: if we didn't scan all the hash
|
chains for expired entries do that now. If we already tried that
|
then it's time to start spilling things. */
|
|
if (new->flags&(F_FORWARD | F_REVERSE)) {
|
/* If free_avail set, we believe that an entry has been freed.
|
Bugs have been known to make this not true, resulting in
|
a tight loop here. If that happens, abandon the
|
insert. Once in this state, all inserts will probably fail. */
|
if (free_avail) {
|
insert_error = 1;
|
return NULL;
|
}
|
|
if (freed_all) {
|
free_avail = 1; /* Must be free space now. */
|
cache_scan_free(cache_get_name(new), &new->addr.addr, now, new->flags);
|
cache_live_freed++;
|
} else {
|
cache_scan_free(NULL, NULL, now, 0);
|
freed_all = 1;
|
}
|
continue;
|
}
|
|
/* Check if we need to and can allocate extra memory for a long name.
|
If that fails, give up now. */
|
if (name && (strlen(name) > SMALLDNAME - 1)) {
|
if (big_free) {
|
big_name = big_free;
|
big_free = big_free->next;
|
} else if (!bignames_left ||
|
!(big_name = (union bigname*) whine_malloc(sizeof(union bigname)))) {
|
insert_error = 1;
|
return NULL;
|
} else
|
bignames_left--;
|
}
|
|
/* Got the rest: finally grab entry. */
|
cache_unlink(new);
|
break;
|
}
|
|
new->flags = flags;
|
if (big_name) {
|
new->name.bname = big_name;
|
new->flags |= F_BIGNAME;
|
}
|
|
if (name)
|
strcpy(cache_get_name(new), name);
|
else
|
*cache_get_name(new) = 0;
|
|
if (addr)
|
new->addr.addr = *addr;
|
else
|
new->addr.cname.cache = NULL;
|
|
new->ttd = now + (time_t) ttl;
|
new->next = new_chain;
|
new_chain = new;
|
|
return new;
|
}
|
|
/* after end of insertion, commit the new entries */
|
void cache_end_insert(void) {
|
if (insert_error) return;
|
|
while (new_chain) {
|
struct crec* tmp = new_chain->next;
|
/* drop CNAMEs which didn't find a target. */
|
if (is_outdated_cname_pointer(new_chain))
|
cache_free(new_chain);
|
else {
|
cache_hash(new_chain);
|
cache_link(new_chain);
|
cache_inserted++;
|
}
|
new_chain = tmp;
|
}
|
new_chain = NULL;
|
}
|
|
struct crec* cache_find_by_name(struct crec* crecp, char* name, time_t now, unsigned short prot) {
|
struct crec* ans;
|
|
if (crecp) /* iterating */
|
ans = crecp->next;
|
else {
|
/* first search, look for relevant entries and push to top of list
|
also free anything which has expired */
|
struct crec *next, **up, **insert = NULL, **chainp = &ans;
|
int ins_flags = 0;
|
|
for (up = hash_bucket(name), crecp = *up; crecp; crecp = next) {
|
next = crecp->hash_next;
|
|
if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp)) {
|
if ((crecp->flags & F_FORWARD) && (crecp->flags & prot) &&
|
hostname_isequal(cache_get_name(crecp), name)) {
|
if (crecp->flags & (F_HOSTS | F_DHCP)) {
|
*chainp = crecp;
|
chainp = &crecp->next;
|
} else {
|
cache_unlink(crecp);
|
cache_link(crecp);
|
}
|
|
/* Move all but the first entry up the hash chain
|
this implements round-robin.
|
Make sure that re-ordering doesn't break the hash-chain
|
order invariants.
|
*/
|
if (insert && (crecp->flags & (F_REVERSE | F_IMMORTAL)) == ins_flags) {
|
*up = crecp->hash_next;
|
crecp->hash_next = *insert;
|
*insert = crecp;
|
insert = &crecp->hash_next;
|
} else {
|
if (!insert) {
|
insert = up;
|
ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL);
|
}
|
up = &crecp->hash_next;
|
}
|
} else
|
/* case : not expired, incorrect entry. */
|
up = &crecp->hash_next;
|
} else {
|
/* expired entry, free it */
|
*up = crecp->hash_next;
|
if (!(crecp->flags & (F_HOSTS | F_DHCP))) {
|
cache_unlink(crecp);
|
cache_free(crecp);
|
}
|
}
|
}
|
|
*chainp = cache_head;
|
}
|
|
if (ans && (ans->flags & F_FORWARD) && (ans->flags & prot) &&
|
hostname_isequal(cache_get_name(ans), name))
|
return ans;
|
|
return NULL;
|
}
|
|
struct crec* cache_find_by_addr(struct crec* crecp, struct all_addr* addr, time_t now,
|
unsigned short prot) {
|
struct crec* ans;
|
#ifdef HAVE_IPV6
|
int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ;
|
#else
|
int addrlen = INADDRSZ;
|
#endif
|
|
if (crecp) /* iterating */
|
ans = crecp->next;
|
else {
|
/* first search, look for relevant entries and push to top of list
|
also free anything which has expired. All the reverse entries are at the
|
start of the hash chain, so we can give up when we find the first
|
non-REVERSE one. */
|
int i;
|
struct crec **up, **chainp = &ans;
|
|
for (i = 0; i < hash_size; i++)
|
for (crecp = hash_table[i], up = &hash_table[i]; crecp && (crecp->flags & F_REVERSE);
|
crecp = crecp->hash_next)
|
if (!is_expired(now, crecp)) {
|
if ((crecp->flags & prot) && memcmp(&crecp->addr.addr, addr, addrlen) == 0) {
|
if (crecp->flags & (F_HOSTS | F_DHCP)) {
|
*chainp = crecp;
|
chainp = &crecp->next;
|
} else {
|
cache_unlink(crecp);
|
cache_link(crecp);
|
}
|
}
|
up = &crecp->hash_next;
|
} else {
|
*up = crecp->hash_next;
|
if (!(crecp->flags & (F_HOSTS | F_DHCP))) {
|
cache_unlink(crecp);
|
cache_free(crecp);
|
}
|
}
|
|
*chainp = cache_head;
|
}
|
|
if (ans && (ans->flags & F_REVERSE) && (ans->flags & prot) &&
|
memcmp(&ans->addr.addr, addr, addrlen) == 0)
|
return ans;
|
|
return NULL;
|
}
|
|
static void add_hosts_entry(struct crec* cache, struct all_addr* addr, int addrlen,
|
unsigned short flags, int index, int addr_dup) {
|
struct crec* lookup = cache_find_by_name(NULL, cache->name.sname, 0, flags & (F_IPV4 | F_IPV6));
|
int i, nameexists = 0;
|
struct cname* a;
|
|
/* Remove duplicates in hosts files. */
|
if (lookup && (lookup->flags & F_HOSTS)) {
|
nameexists = 1;
|
if (memcmp(&lookup->addr.addr, addr, addrlen) == 0) {
|
free(cache);
|
return;
|
}
|
}
|
|
/* Ensure there is only one address -> name mapping (first one trumps)
|
We do this by steam here, first we see if the address is the same as
|
the last one we saw, which eliminates most in the case of an ad-block
|
file with thousands of entries for the same address.
|
Then we search and bail at the first matching address that came from
|
a HOSTS file. Since the first host entry gets reverse, we know
|
then that it must exist without searching exhaustively for it. */
|
|
if (addr_dup)
|
flags &= ~F_REVERSE;
|
else
|
for (i = 0; i < hash_size; i++) {
|
for (lookup = hash_table[i]; lookup; lookup = lookup->hash_next)
|
if ((lookup->flags & F_HOSTS) && (lookup->flags & flags & (F_IPV4 | F_IPV6)) &&
|
memcmp(&lookup->addr.addr, addr, addrlen) == 0) {
|
flags &= ~F_REVERSE;
|
break;
|
}
|
if (lookup) break;
|
}
|
|
cache->flags = flags;
|
cache->uid = index;
|
memcpy(&cache->addr.addr, addr, addrlen);
|
cache_hash(cache);
|
|
/* don't need to do alias stuff for second and subsequent addresses. */
|
if (!nameexists)
|
for (a = daemon->cnames; a; a = a->next)
|
if (hostname_isequal(cache->name.sname, a->target) &&
|
(lookup = whine_malloc(sizeof(struct crec)))) {
|
lookup->flags = F_FORWARD | F_IMMORTAL | F_CONFIG | F_HOSTS | F_CNAME;
|
lookup->name.namep = a->alias;
|
lookup->addr.cname.cache = cache;
|
lookup->addr.cname.uid = index;
|
cache_hash(lookup);
|
}
|
}
|
|
static int eatspace(FILE* f) {
|
int c, nl = 0;
|
|
while (1) {
|
if ((c = getc(f)) == '#')
|
while (c != '\n' && c != EOF) c = getc(f);
|
|
if (c == EOF) return 1;
|
|
if (!isspace(c)) {
|
ungetc(c, f);
|
return nl;
|
}
|
|
if (c == '\n') nl = 1;
|
}
|
}
|
|
static int gettok(FILE* f, char* token) {
|
int c, count = 0;
|
|
while (1) {
|
if ((c = getc(f)) == EOF) return (count == 0) ? EOF : 1;
|
|
if (isspace(c) || c == '#') {
|
ungetc(c, f);
|
return eatspace(f);
|
}
|
|
if (count < (MAXDNAME - 1)) {
|
token[count++] = c;
|
token[count] = 0;
|
}
|
}
|
}
|
|
static int read_hostsfile(char* filename, int index, int cache_size) {
|
FILE* f = fopen(filename, "r");
|
char *token = daemon->namebuff, *domain_suffix = NULL;
|
int addr_count = 0, name_count = cache_size, lineno = 0;
|
unsigned short flags = 0, saved_flags = 0;
|
struct all_addr addr, saved_addr;
|
int atnl, addrlen = 0, addr_dup;
|
|
if (!f) {
|
my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno));
|
return 0;
|
}
|
|
eatspace(f);
|
|
while ((atnl = gettok(f, token)) != EOF) {
|
addr_dup = 0;
|
lineno++;
|
|
#ifdef HAVE_IPV6
|
if (inet_pton(AF_INET, token, &addr) > 0) {
|
flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
|
addrlen = INADDRSZ;
|
domain_suffix = get_domain(addr.addr.addr4);
|
} else if (inet_pton(AF_INET6, token, &addr) > 0) {
|
flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
|
addrlen = IN6ADDRSZ;
|
domain_suffix = daemon->domain_suffix;
|
}
|
#else
|
if ((addr.addr.addr4.s_addr = inet_addr(token)) != (in_addr_t) -1) {
|
flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
|
addrlen = INADDRSZ;
|
domain_suffix = get_domain(addr.addr.addr4);
|
}
|
#endif
|
else {
|
my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno);
|
while (atnl == 0) atnl = gettok(f, token);
|
continue;
|
}
|
|
if (saved_flags == flags && memcmp(&addr, &saved_addr, addrlen) == 0)
|
addr_dup = 1;
|
else {
|
saved_flags = flags;
|
saved_addr = addr;
|
}
|
|
addr_count++;
|
|
/* rehash every 1000 names. */
|
if ((name_count - cache_size) > 1000) {
|
rehash(name_count);
|
cache_size = name_count;
|
}
|
|
while (atnl == 0) {
|
struct crec* cache;
|
int fqdn, nomem;
|
char* canon;
|
|
if ((atnl = gettok(f, token)) == EOF) break;
|
|
fqdn = !!strchr(token, '.');
|
|
if ((canon = canonicalise(token, &nomem))) {
|
/* If set, add a version of the name with a default domain appended */
|
if ((daemon->options & OPT_EXPAND) && domain_suffix && !fqdn &&
|
(cache = whine_malloc(sizeof(struct crec) + strlen(canon) + 2 +
|
strlen(domain_suffix) - SMALLDNAME))) {
|
strcpy(cache->name.sname, canon);
|
strcat(cache->name.sname, ".");
|
strcat(cache->name.sname, domain_suffix);
|
add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup);
|
addr_dup = 1;
|
name_count++;
|
}
|
if ((cache = whine_malloc(sizeof(struct crec) + strlen(canon) + 1 - SMALLDNAME))) {
|
strcpy(cache->name.sname, canon);
|
add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup);
|
name_count++;
|
}
|
free(canon);
|
|
} else if (!nomem)
|
my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno);
|
}
|
}
|
|
fclose(f);
|
rehash(name_count);
|
|
my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count);
|
|
return name_count;
|
}
|
|
void cache_reload(void) {
|
struct crec *cache, **up, *tmp;
|
int i, total_size = daemon->cachesize;
|
struct hostsfile* ah;
|
|
cache_inserted = cache_live_freed = 0;
|
|
for (i = 0; i < hash_size; i++)
|
for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp) {
|
tmp = cache->hash_next;
|
if (cache->flags & F_HOSTS) {
|
*up = cache->hash_next;
|
free(cache);
|
} else if (!(cache->flags & F_DHCP)) {
|
*up = cache->hash_next;
|
if (cache->flags & F_BIGNAME) {
|
cache->name.bname->next = big_free;
|
big_free = cache->name.bname;
|
}
|
cache->flags = 0;
|
} else
|
up = &cache->hash_next;
|
}
|
|
if ((daemon->options & OPT_NO_HOSTS) && !daemon->addn_hosts) {
|
if (daemon->cachesize > 0) my_syslog(LOG_INFO, _("cleared cache"));
|
return;
|
}
|
|
if (!(daemon->options & OPT_NO_HOSTS)) total_size = read_hostsfile(HOSTSFILE, 0, total_size);
|
|
for (i = 0, ah = daemon->addn_hosts; ah; ah = ah->next) {
|
if (i <= ah->index) i = ah->index + 1;
|
|
if (ah->flags & AH_DIR)
|
ah->flags |= AH_INACTIVE;
|
else
|
ah->flags &= ~AH_INACTIVE;
|
}
|
|
for (ah = daemon->addn_hosts; ah; ah = ah->next)
|
if (!(ah->flags & AH_INACTIVE)) {
|
struct stat buf;
|
if (stat(ah->fname, &buf) != -1 && S_ISDIR(buf.st_mode)) {
|
DIR* dir_stream;
|
struct dirent* ent;
|
|
/* don't read this as a file */
|
ah->flags |= AH_INACTIVE;
|
|
if (!(dir_stream = opendir(ah->fname)))
|
my_syslog(LOG_ERR, _("cannot access directory %s: %s"), ah->fname,
|
strerror(errno));
|
else {
|
while ((ent = readdir(dir_stream))) {
|
size_t lendir = strlen(ah->fname);
|
size_t lenfile = strlen(ent->d_name);
|
struct hostsfile* ah1;
|
char* path;
|
|
/* ignore emacs backups and dotfiles */
|
if (lenfile == 0 || ent->d_name[lenfile - 1] == '~' ||
|
(ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') ||
|
ent->d_name[0] == '.')
|
continue;
|
|
/* see if we have an existing record.
|
dir is ah->fname
|
file is ent->d_name
|
path to match is ah1->fname */
|
|
for (ah1 = daemon->addn_hosts; ah1; ah1 = ah1->next) {
|
if (lendir < strlen(ah1->fname) &&
|
strstr(ah1->fname, ah->fname) == ah1->fname &&
|
ah1->fname[lendir] == '/' &&
|
strcmp(ah1->fname + lendir + 1, ent->d_name) == 0) {
|
ah1->flags &= ~AH_INACTIVE;
|
break;
|
}
|
}
|
|
/* make new record */
|
if (!ah1) {
|
if (!(ah1 = whine_malloc(sizeof(struct hostsfile)))) continue;
|
|
if (!(path = whine_malloc(lendir + lenfile + 2))) {
|
free(ah1);
|
continue;
|
}
|
|
strcpy(path, ah->fname);
|
strcat(path, "/");
|
strcat(path, ent->d_name);
|
ah1->fname = path;
|
ah1->index = i++;
|
ah1->flags = AH_DIR;
|
ah1->next = daemon->addn_hosts;
|
daemon->addn_hosts = ah1;
|
}
|
|
/* inactivate record if not regular file */
|
if ((ah1->flags & AH_DIR) && stat(ah1->fname, &buf) != -1 &&
|
!S_ISREG(buf.st_mode))
|
ah1->flags |= AH_INACTIVE;
|
}
|
closedir(dir_stream);
|
}
|
}
|
}
|
|
for (ah = daemon->addn_hosts; ah; ah = ah->next)
|
if (!(ah->flags & AH_INACTIVE))
|
total_size = read_hostsfile(ah->fname, ah->index, total_size);
|
}
|
|
char* get_domain(struct in_addr addr) {
|
struct cond_domain* c;
|
|
for (c = daemon->cond_domain; c; c = c->next)
|
if (ntohl(addr.s_addr) >= ntohl(c->start.s_addr) &&
|
ntohl(addr.s_addr) <= ntohl(c->end.s_addr))
|
return c->domain;
|
|
return daemon->domain_suffix;
|
}
|
|
#ifdef HAVE_DHCP
|
void cache_unhash_dhcp(void) {
|
struct crec *cache, **up;
|
int i;
|
|
for (i = 0; i < hash_size; i++)
|
for (cache = hash_table[i], up = &hash_table[i]; cache; cache = cache->hash_next)
|
if (cache->flags & F_DHCP) {
|
*up = cache->hash_next;
|
cache->next = dhcp_spare;
|
dhcp_spare = cache;
|
} else
|
up = &cache->hash_next;
|
}
|
|
void cache_add_dhcp_entry(char* host_name, struct in_addr* host_address, time_t ttd) {
|
struct crec *crec = NULL, *aliasc;
|
unsigned short flags = F_DHCP | F_FORWARD | F_IPV4 | F_REVERSE;
|
int in_hosts = 0;
|
struct cname* a;
|
|
while ((crec = cache_find_by_name(crec, host_name, 0, F_IPV4 | F_CNAME))) {
|
/* check all addresses associated with name */
|
if (crec->flags & F_HOSTS) {
|
if (crec->addr.addr.addr.addr4.s_addr != host_address->s_addr) {
|
strcpy(daemon->namebuff, inet_ntoa(crec->addr.addr.addr.addr4));
|
my_syslog(LOG_WARNING,
|
_("not giving name %s to the DHCP lease of %s because "
|
"the name exists in %s with address %s"),
|
host_name, inet_ntoa(*host_address), record_source(crec->uid),
|
daemon->namebuff);
|
return;
|
} else
|
/* if in hosts, don't need DHCP record */
|
in_hosts = 1;
|
} else if (!(crec->flags & F_DHCP)) {
|
cache_scan_free(host_name, NULL, 0, crec->flags & (F_IPV4 | F_CNAME | F_FORWARD));
|
/* scan_free deletes all addresses associated with name */
|
break;
|
}
|
}
|
|
if (in_hosts) return;
|
|
if ((crec = cache_find_by_addr(NULL, (struct all_addr*) host_address, 0, F_IPV4))) {
|
if (crec->flags & F_NEG)
|
cache_scan_free(NULL, (struct all_addr*) host_address, 0, F_IPV4 | F_REVERSE);
|
else
|
/* avoid multiple reverse mappings */
|
flags &= ~F_REVERSE;
|
}
|
|
if ((crec = dhcp_spare))
|
dhcp_spare = dhcp_spare->next;
|
else /* need new one */
|
crec = whine_malloc(sizeof(struct crec));
|
|
if (crec) /* malloc may fail */
|
{
|
crec->flags = flags;
|
if (ttd == 0)
|
crec->flags |= F_IMMORTAL;
|
else
|
crec->ttd = ttd;
|
crec->addr.addr.addr.addr4 = *host_address;
|
crec->name.namep = host_name;
|
crec->uid = uid++;
|
cache_hash(crec);
|
|
for (a = daemon->cnames; a; a = a->next)
|
if (hostname_isequal(host_name, a->target)) {
|
if ((aliasc = dhcp_spare))
|
dhcp_spare = dhcp_spare->next;
|
else /* need new one */
|
aliasc = whine_malloc(sizeof(struct crec));
|
|
if (aliasc) {
|
aliasc->flags = F_FORWARD | F_CONFIG | F_DHCP | F_CNAME;
|
if (ttd == 0)
|
aliasc->flags |= F_IMMORTAL;
|
else
|
aliasc->ttd = ttd;
|
aliasc->name.namep = a->alias;
|
aliasc->addr.cname.cache = crec;
|
aliasc->addr.cname.uid = crec->uid;
|
cache_hash(aliasc);
|
}
|
}
|
}
|
}
|
#endif
|
|
void dump_cache(time_t now) {
|
struct server *serv, *serv1;
|
|
my_syslog(LOG_INFO, _("time %lu"), (unsigned long) now);
|
my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."),
|
daemon->cachesize, cache_live_freed, cache_inserted);
|
my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"),
|
daemon->queries_forwarded, daemon->local_answer);
|
|
if (!addrbuff && !(addrbuff = whine_malloc(ADDRSTRLEN))) return;
|
|
/* sum counts from different records for same server */
|
for (serv = daemon->servers; serv; serv = serv->next) serv->flags &= ~SERV_COUNTED;
|
|
for (serv = daemon->servers; serv; serv = serv->next)
|
if (!(serv->flags & (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED))) {
|
int port;
|
unsigned int queries = 0, failed_queries = 0;
|
for (serv1 = serv; serv1; serv1 = serv1->next)
|
if (!(serv1->flags & (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED)) &&
|
sockaddr_isequal(&serv->addr, &serv1->addr)) {
|
serv1->flags |= SERV_COUNTED;
|
queries += serv1->queries;
|
failed_queries += serv1->failed_queries;
|
}
|
port = prettyprint_addr(&serv->addr, addrbuff);
|
my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), addrbuff,
|
port, queries, failed_queries);
|
}
|
|
if ((daemon->options & (OPT_DEBUG | OPT_LOG))) {
|
struct crec* cache;
|
int i;
|
my_syslog(LOG_DEBUG,
|
"Host Address Flags "
|
" Expires");
|
|
for (i = 0; i < hash_size; i++)
|
for (cache = hash_table[i]; cache; cache = cache->hash_next) {
|
char *a, *p = daemon->namebuff;
|
p += sprintf(p, "%-40.40s ", cache_get_name(cache));
|
if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD))
|
a = "";
|
else if (cache->flags & F_CNAME) {
|
a = "";
|
if (!is_outdated_cname_pointer(cache))
|
a = cache_get_name(cache->addr.cname.cache);
|
}
|
#ifdef HAVE_IPV6
|
else {
|
a = addrbuff;
|
if (cache->flags & F_IPV4)
|
inet_ntop(AF_INET, &cache->addr.addr, addrbuff, ADDRSTRLEN);
|
else if (cache->flags & F_IPV6)
|
inet_ntop(AF_INET6, &cache->addr.addr, addrbuff, ADDRSTRLEN);
|
}
|
#else
|
else
|
a = inet_ntoa(cache->addr.addr.addr.addr4);
|
#endif
|
p += sprintf(
|
p, "%-30.30s %s%s%s%s%s%s%s%s%s%s ", a, cache->flags & F_IPV4 ? "4" : "",
|
cache->flags & F_IPV6 ? "6" : "", cache->flags & F_CNAME ? "C" : "",
|
cache->flags & F_FORWARD ? "F" : " ", cache->flags & F_REVERSE ? "R" : " ",
|
cache->flags & F_IMMORTAL ? "I" : " ", cache->flags & F_DHCP ? "D" : " ",
|
cache->flags & F_NEG ? "N" : " ", cache->flags & F_NXDOMAIN ? "X" : " ",
|
cache->flags & F_HOSTS ? "H" : " ");
|
#ifdef HAVE_BROKEN_RTC
|
p += sprintf(p, "%lu",
|
cache->flags & F_IMMORTAL ? 0 : (unsigned long) (cache->ttd - now));
|
#else
|
p += sprintf(p, "%s", cache->flags & F_IMMORTAL ? "\n" : ctime(&(cache->ttd)));
|
/* ctime includes trailing \n - eat it */
|
*(p - 1) = 0;
|
#endif
|
my_syslog(LOG_DEBUG, daemon->namebuff);
|
}
|
}
|
}
|
|
char* record_source(int index) {
|
struct hostsfile* ah;
|
|
if (index == 0) return HOSTSFILE;
|
|
for (ah = daemon->addn_hosts; ah; ah = ah->next)
|
if (ah->index == index) return ah->fname;
|
|
return "<unknown>";
|
}
|
|
void querystr(char* str, unsigned short type) {
|
unsigned int i;
|
|
sprintf(str, "query[type=%d]", type);
|
for (i = 0; i < (sizeof(typestr) / sizeof(typestr[0])); i++)
|
if (typestr[i].type == type) sprintf(str, "query[%s]", typestr[i].name);
|
}
|
|
void log_query(unsigned short flags, char* name, struct all_addr* addr, char* arg) {
|
char *source, *dest = addrbuff;
|
char* verb = "is";
|
|
if (!(daemon->options & OPT_LOG)) return;
|
|
if (addr) {
|
#ifdef HAVE_IPV6
|
/* TODO: support scoped addresses. struct all_addr doesn't store scope IDs. */
|
inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, addr, addrbuff, ADDRSTRLEN);
|
#else
|
strncpy(addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
|
#endif
|
}
|
|
if (flags & F_REVERSE) {
|
dest = name;
|
name = addrbuff;
|
}
|
|
if (flags & F_NEG) {
|
if (flags & F_NXDOMAIN) {
|
if (flags & F_IPV4)
|
dest = "NXDOMAIN-IPv4";
|
else if (flags & F_IPV6)
|
dest = "NXDOMAIN-IPv6";
|
else
|
dest = "NXDOMAIN";
|
} else {
|
if (flags & F_IPV4)
|
dest = "NODATA-IPv4";
|
else if (flags & F_IPV6)
|
dest = "NODATA-IPv6";
|
else
|
dest = "NODATA";
|
}
|
} else if (flags & F_CNAME) {
|
/* nasty abuse of NXDOMAIN and CNAME flags */
|
if (flags & F_NXDOMAIN)
|
dest = arg;
|
else
|
dest = "<CNAME>";
|
}
|
|
if (flags & F_CONFIG)
|
source = "config";
|
else if (flags & F_DHCP)
|
source = "DHCP";
|
else if (flags & F_HOSTS)
|
source = arg;
|
else if (flags & F_UPSTREAM)
|
source = "reply";
|
else if (flags & F_SERVER) {
|
source = "forwarded";
|
verb = "to";
|
} else if (flags & F_QUERY) {
|
source = arg;
|
verb = "from";
|
} else
|
source = "cached";
|
|
if (strlen(name) == 0) name = ".";
|
|
my_syslog(LOG_DEBUG, "%s %s %s %s", source, name, verb, dest);
|
}
|