[T106][ZXW-22]7520V3SCV2.01.01.02P42U09_VEC_V0.8_AP_VEC origin source commit
Change-Id: Ic6e05d89ecd62fc34f82b23dcf306c93764aec4b
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c b/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c
new file mode 100755
index 0000000..8add610
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c
@@ -0,0 +1,2031 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 insert_error;
+static union bigname *big_free = NULL;
+static int bignames_left, hash_size;
+
+static void make_non_terminals(struct crec *source);
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags);
+
+/* 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" },
+ { 29, "LOC" },
+ { 33, "SRV" },
+ { 35, "NAPTR" },
+ { 36, "KX" },
+ { 37, "CERT" },
+ { 38, "A6" },
+ { 39, "DNAME" },
+ { 41, "OPT" },
+ { 43, "DS" },
+ { 46, "RRSIG" },
+ { 47, "NSEC" },
+ { 48, "DNSKEY" },
+ { 50, "NSEC3" },
+ { 51, "NSEC3PARAM" },
+ { 52, "TLSA" },
+ { 53, "SMIMEA" },
+ { 55, "HIP" },
+ { 249, "TKEY" },
+ { 250, "TSIG" },
+ { 251, "IXFR" },
+ { 252, "AXFR" },
+ { 253, "MAILB" },
+ { 254, "MAILA" },
+ { 255, "ANY" },
+ { 257, "CAA" }
+};
+
+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 next_uid(struct crec *crecp)
+{
+ static unsigned int uid = 0;
+
+ if (crecp->uid == UID_NONE)
+ {
+ uid++;
+
+ /* uid == 0 used to indicate CNAME to interface name. */
+ if (uid == UID_NONE)
+ uid++;
+
+ crecp->uid = uid;
+ }
+}
+
+void cache_init(void)
+{
+ struct crec *crecp;
+ int i;
+
+ 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_NONE;
+ }
+ }
+
+ /* 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_blockdata_free(struct crec *crecp)
+{
+ if (!(crecp->flags & F_NEG))
+ {
+ if (crecp->flags & F_SRV)
+ blockdata_free(crecp->addr.srv.target);
+#ifdef HAVE_DNSSEC
+ else if (crecp->flags & F_DNSKEY)
+ blockdata_free(crecp->addr.key.keydata);
+ else if (crecp->flags & F_DS)
+ blockdata_free(crecp->addr.ds.keydata);
+#endif
+ }
+}
+
+static void cache_free(struct crec *crecp)
+{
+ crecp->flags &= ~F_FORWARD;
+ crecp->flags &= ~F_REVERSE;
+ crecp->uid = UID_NONE; /* 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;
+ }
+
+ cache_blockdata_free(crecp);
+}
+
+/* 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_NAMEP)
+ return crecp->name.namep;
+
+ return crecp->name.sname;
+}
+
+char *cache_get_cname_target(struct crec *crecp)
+{
+ if (crecp->addr.cname.is_name_ptr)
+ return crecp->addr.cname.target.name;
+ else
+ return cache_get_name(crecp->addr.cname.target.cache);
+}
+
+
+
+struct crec *cache_enumerate(int init)
+{
+ static int bucket;
+ static struct crec *cache;
+
+ if (init)
+ {
+ bucket = 0;
+ cache = NULL;
+ }
+ else if (cache && cache->hash_next)
+ cache = cache->hash_next;
+ else
+ {
+ cache = NULL;
+ while (bucket < hash_size)
+ if ((cache = hash_table[bucket++]))
+ break;
+ }
+
+ return cache;
+}
+
+static int is_outdated_cname_pointer(struct crec *crecp)
+{
+ if (!(crecp->flags & F_CNAME) || crecp->addr.cname.is_name_ptr)
+ return 0;
+
+ /* NB. record may be reused as DS or DNSKEY, where uid is
+ overloaded for something completely different */
+ if (crecp->addr.cname.target.cache &&
+ !(crecp->addr.cname.target.cache->flags & (F_DNSKEY | F_DS)) &&
+ crecp->addr.cname.uid == crecp->addr.cname.target.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 struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned short class, time_t now,
+ unsigned int flags, struct crec **target_crec, unsigned int *target_uid)
+{
+ /* 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 a non-NULL pointer
+ to a cache entry 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.
+
+ If we free a crec which is a CNAME target, return the entry and uid in target_crec and target_uid.
+ This entry will get re-used with the same name, to preserve CNAMEs. */
+
+ struct crec *crecp, **up;
+
+ (void)class;
+
+ if (flags & F_FORWARD)
+ {
+ for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
+ {
+ if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
+ {
+ /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
+ if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_SRV)) ||
+ (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))))
+ {
+ if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+ return crecp;
+ *up = crecp->hash_next;
+ /* If this record is for the name we're inserting and is the target
+ of a CNAME record. Make the new record for the same name, in the same
+ crec, with the same uid to avoid breaking the existing CNAME. */
+ if (crecp->uid != UID_NONE)
+ {
+ if (target_crec)
+ *target_crec = crecp;
+ if (target_uid)
+ *target_uid = crecp->uid;
+ }
+ cache_unlink(crecp);
+ cache_free(crecp);
+ continue;
+ }
+
+#ifdef HAVE_DNSSEC
+ /* Deletion has to be class-sensitive for DS and DNSKEY */
+ if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == class)
+ {
+ if (crecp->flags & F_CONFIG)
+ return crecp;
+ *up = crecp->hash_next;
+ cache_unlink(crecp);
+ cache_free(crecp);
+ continue;
+ }
+#endif
+ }
+
+ if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+ {
+ *up = crecp->hash_next;
+ if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ continue;
+ }
+
+ up = &crecp->hash_next;
+ }
+ }
+ else
+ {
+ int i;
+ int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+
+ 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 | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ }
+ else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) &&
+ (flags & crecp->flags & F_REVERSE) &&
+ (flags & crecp->flags & (F_IPV4 | F_IPV6)) &&
+ memcmp(&crecp->addr, addr, addrlen) == 0)
+ {
+ *up = crecp->hash_next;
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ else
+ up = &crecp->hash_next;
+ }
+
+ return NULL;
+}
+
+/* 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, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags)
+{
+#ifdef HAVE_DNSSEC
+ if (flags & (F_DNSKEY | F_DS))
+ {
+ /* The DNSSEC validation process works by getting needed records into the
+ cache, then retrying the validation until they are all in place.
+ This can be messed up by very short TTLs, and _really_ messed up by
+ zero TTLs, so we force the TTL to be at least long enough to do a validation.
+ Ideally, we should use some kind of reference counting so that records are
+ locked until the validation that asked for them is complete, but this
+ is much easier, and just as effective. */
+ if (ttl < DNSSEC_MIN_TTL)
+ ttl = DNSSEC_MIN_TTL;
+ }
+ else
+#endif
+ {
+ if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
+ ttl = daemon->max_cache_ttl;
+ if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl)
+ ttl = daemon->min_cache_ttl;
+ }
+
+ return really_insert(name, addr, class, now, ttl, flags);
+}
+
+
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags)
+{
+ struct crec *new, *target_crec = NULL;
+ union bigname *big_name = NULL;
+ int freed_all = flags & F_REVERSE;
+ int free_avail = 0;
+ unsigned int target_uid;
+
+ /* if previous insertion failed give up now. */
+ if (insert_error)
+ return NULL;
+
+ /* we don't cache zero-TTL records. */
+ if (ttl == 0)
+ {
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* First remove any expired entries and entries for the name/address we
+ are currently inserting. */
+ if ((new = cache_scan_free(name, addr, class, now, flags, &target_crec, &target_uid)))
+ {
+ /* We're trying to insert a record over one from
+ /etc/hosts or DHCP, or other config. If the
+ existing record is for an A or AAAA or CNAME and
+ the record we're trying to insert is the same,
+ just drop the insert, but don't error the whole process. */
+ if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD) && addr)
+ {
+ if ((flags & F_IPV4) && (new->flags & F_IPV4) &&
+ new->addr.addr4.s_addr == addr->addr4.s_addr)
+ return new;
+ else if ((flags & F_IPV6) && (new->flags & F_IPV6) &&
+ IN6_ARE_ADDR_EQUAL(&new->addr.addr6, &addr->addr6))
+ return new;
+ }
+
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* Now get a cache entry from the end of the LRU list */
+ if (!target_crec)
+ while (1) {
+ if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
+ {
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* Free entry at end of LRU list, use it. */
+ if (!(new->flags & (F_FORWARD | F_REVERSE)))
+ break;
+
+ /* 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 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)
+ {
+ static int warned = 0;
+ if (!warned)
+ {
+ my_syslog(LOG_ERR, _("Internal error in cache."));
+ warned = 1;
+ }
+ insert_error = 1;
+ return NULL;
+ }
+
+ if (freed_all)
+ {
+ /* For DNSSEC records, uid holds class. */
+ free_avail = 1; /* Must be free space now. */
+ cache_scan_free(cache_get_name(new), &new->addr, new->uid, now, new->flags, NULL, NULL);
+ daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++;
+ }
+ else
+ {
+ cache_scan_free(NULL, NULL, class, now, 0, NULL, NULL);
+ freed_all = 1;
+ }
+ }
+
+ /* Check if we need to and can allocate extra memory for a long name.
+ If that fails, give up now, always succeed for DNSSEC records. */
+ if (name && (strlen(name) > SMALLDNAME-1))
+ {
+ if (big_free)
+ {
+ big_name = big_free;
+ big_free = big_free->next;
+ }
+ else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
+ !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
+ {
+ insert_error = 1;
+ return NULL;
+ }
+ else if (bignames_left != 0)
+ bignames_left--;
+
+ }
+
+ /* If we freed a cache entry for our name which was a CNAME target, use that.
+ and preserve the uid, so that existing CNAMES are not broken. */
+ if (target_crec)
+ {
+ new = target_crec;
+ new->uid = target_uid;
+ }
+
+ /* Got the rest: finally grab entry. */
+ cache_unlink(new);
+
+ 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;
+
+#ifdef HAVE_DNSSEC
+ if (flags & (F_DS | F_DNSKEY))
+ new->uid = class;
+#endif
+
+ if (addr)
+ new->addr = *addr;
+
+ 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);
+ daemon->metrics[METRIC_DNS_CACHE_INSERTED]++;
+
+ /* If we're a child process, send this cache entry up the pipe to the master.
+ The marshalling process is rather nasty. */
+ if (daemon->pipe_to_parent != -1)
+ {
+ char *name = cache_get_name(new_chain);
+ ssize_t m = strlen(name);
+ unsigned int flags = new_chain->flags;
+#ifdef HAVE_DNSSEC
+ u16 class = new_chain->uid;
+#endif
+
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0);
+
+ if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+ read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
+ if (flags & F_SRV)
+ {
+ /* A negative SRV entry is possible and has no data, obviously. */
+ if (!(flags & F_NEG))
+ blockdata_write(new_chain->addr.srv.target, new_chain->addr.srv.targetlen, daemon->pipe_to_parent);
+ }
+#ifdef HAVE_DNSSEC
+ if (flags & F_DNSKEY)
+ {
+ read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+ blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent);
+ }
+ else if (flags & F_DS)
+ {
+ read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+ /* A negative DS entry is possible and has no data, obviously. */
+ if (!(flags & F_NEG))
+ blockdata_write(new_chain->addr.ds.keydata, new_chain->addr.ds.keylen, daemon->pipe_to_parent);
+ }
+#endif
+ }
+ }
+
+ new_chain = tmp;
+ }
+
+ /* signal end of cache insert in master process */
+ if (daemon->pipe_to_parent != -1)
+ {
+ ssize_t m = -1;
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+ }
+
+ new_chain = NULL;
+}
+
+
+/* A marshalled cache entry arrives on fd, read, unmarshall and insert into cache of master process. */
+int cache_recv_insert(time_t now, int fd)
+{
+ ssize_t m;
+ union all_addr addr;
+ unsigned long ttl;
+ time_t ttd;
+ unsigned int flags;
+ struct crec *crecp = NULL;
+
+ cache_start_insert();
+
+ while(1)
+ {
+
+ if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+ return 0;
+
+ if (m == -1)
+ {
+ cache_end_insert();
+ return 1;
+ }
+
+ if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
+ !read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
+ !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1))
+ return 0;
+
+ daemon->namebuff[m] = 0;
+
+ ttl = difftime(ttd, now);
+
+ if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+ {
+ unsigned short class = C_IN;
+
+ if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
+ return 0;
+
+ if ((flags & F_SRV) && !(flags & F_NEG) && !(addr.srv.target = blockdata_read(fd, addr.srv.targetlen)))
+ return 0;
+
+#ifdef HAVE_DNSSEC
+ if (flags & F_DNSKEY)
+ {
+ if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+ !(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
+ return 0;
+ }
+ else if (flags & F_DS)
+ {
+ if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+ (!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))))
+ return 0;
+ }
+#endif
+
+ crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
+ }
+ else if (flags & F_CNAME)
+ {
+ struct crec *newc = really_insert(daemon->namebuff, NULL, C_IN, now, ttl, flags);
+ /* This relies on the fact that the target of a CNAME immediately precedes
+ it because of the order of extraction in extract_addresses, and
+ the order reversal on the new_chain. */
+ if (newc)
+ {
+ newc->addr.cname.is_name_ptr = 0;
+
+ if (!crecp)
+ newc->addr.cname.target.cache = NULL;
+ else
+ {
+ next_uid(crecp);
+ newc->addr.cname.target.cache = crecp;
+ newc->addr.cname.uid = crecp->uid;
+ }
+ }
+ }
+ }
+}
+
+int cache_find_non_terminal(char *name, time_t now)
+{
+ struct crec *crecp;
+
+ for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+ if (!is_outdated_cname_pointer(crecp) &&
+ !is_expired(now, crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ !(crecp->flags & F_NXDOMAIN) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ return 1;
+
+ return 0;
+}
+
+struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot)
+{
+ struct crec *ans;
+ int no_rr = prot & F_NO_RR;
+
+ prot &= ~F_NO_RR;
+
+ 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;
+ unsigned 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 | F_CONFIG))
+ {
+ *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 && !no_rr)
+ {
+ 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 | F_CONFIG)))
+ {
+ 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, union all_addr *addr,
+ time_t now, unsigned int prot)
+{
+ struct crec *ans;
+ int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+
+ 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, addrlen) == 0)
+ {
+ if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+ {
+ *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 | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ }
+
+ *chainp = cache_head;
+ }
+
+ if (ans &&
+ (ans->flags & F_REVERSE) &&
+ (ans->flags & prot) &&
+ memcmp(&ans->addr, addr, addrlen) == 0)
+ return ans;
+
+ return NULL;
+}
+
+static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen,
+ unsigned int index, struct crec **rhash, int hashsz)
+{
+ struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6));
+ int i;
+ unsigned int j;
+
+ /* Remove duplicates in hosts files. */
+ if (lookup && (lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0)
+ {
+ free(cache);
+ return;
+ }
+
+ /* Ensure there is only one address -> name mapping (first one trumps)
+ We do this by steam here, The entries are kept in hash chains, linked
+ by ->next (which is unused at this point) held in hash buckets in
+ the array rhash, hashed on address. Note that rhash and the values
+ in ->next are only valid whilst reading hosts files: the buckets are
+ then freed, and the ->next pointer used for other things.
+
+ Only insert each unique address once into this hashing structure.
+
+ This complexity avoids O(n^2) divergent CPU use whilst reading
+ large (10000 entry) hosts files.
+
+ Note that we only do this process when bulk-reading hosts files,
+ for incremental reads, rhash is NULL, and we use cache lookups
+ instead.
+ */
+
+ if (rhash)
+ {
+ /* hash address */
+ for (j = 0, i = 0; i < addrlen; i++)
+ j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
+
+ for (lookup = rhash[j]; lookup; lookup = lookup->next)
+ if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
+ memcmp(&lookup->addr, addr, addrlen) == 0)
+ {
+ cache->flags &= ~F_REVERSE;
+ break;
+ }
+
+ /* maintain address hash chain, insert new unique address */
+ if (!lookup)
+ {
+ cache->next = rhash[j];
+ rhash[j] = cache;
+ }
+ }
+ else
+ {
+ /* incremental read, lookup in cache */
+ lookup = cache_find_by_addr(NULL, addr, 0, cache->flags & (F_IPV4 | F_IPV6));
+ if (lookup && lookup->flags & F_HOSTS)
+ cache->flags &= ~F_REVERSE;
+ }
+
+ cache->uid = index;
+ memcpy(&cache->addr, addr, addrlen);
+ cache_hash(cache);
+ make_non_terminals(cache);
+}
+
+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++;
+ }
+}
+
+static int gettok(FILE *f, char *token)
+{
+ int c, count = 0;
+
+ while (1)
+ {
+ if ((c = getc(f)) == EOF)
+ return (count == 0) ? -1 : 1;
+
+ if (isspace(c) || c == '#')
+ {
+ ungetc(c, f);
+ return eatspace(f);
+ }
+
+ if (count < (MAXDNAME - 1))
+ {
+ token[count++] = c;
+ token[count] = 0;
+ }
+ }
+}
+
+int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz)
+{
+ FILE *f = fopen(filename, "r");
+ char *token = daemon->namebuff, *domain_suffix = NULL;
+ int addr_count = 0, name_count = cache_size, lineno = 1;
+ unsigned int flags = 0;
+ union all_addr addr;
+ int atnl, addrlen = 0;
+
+ if (!f)
+ {
+ my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno));
+ return cache_size;
+ }
+
+ lineno += eatspace(f);
+
+ while ((atnl = gettok(f, token)) != -1)
+ {
+ 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.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 = get_domain6(&addr.addr6);
+ }
+ else
+ {
+ my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno);
+ while (atnl == 0)
+ atnl = gettok(f, token);
+ lineno += atnl;
+ continue;
+ }
+
+ addr_count++;
+
+ /* rehash every 1000 names. */
+ if (rhash && ((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)) == -1)
+ break;
+
+ fqdn = !!strchr(token, '.');
+
+ if ((canon = canonicalise(token, &nomem)))
+ {
+ /* If set, add a version of the name with a default domain appended */
+ if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn &&
+ (cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix))))
+ {
+ strcpy(cache->name.sname, canon);
+ strcat(cache->name.sname, ".");
+ strcat(cache->name.sname, domain_suffix);
+ cache->flags = flags;
+ cache->ttd = daemon->local_ttl;
+ add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+ name_count++;
+ }
+ if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1)))
+ {
+ strcpy(cache->name.sname, canon);
+ cache->flags = flags;
+ cache->ttd = daemon->local_ttl;
+ add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+ name_count++;
+ }
+ free(canon);
+
+ }
+ else if (!nomem)
+ my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno);
+ }
+
+ lineno += atnl;
+ }
+
+ fclose(f);
+
+ if (rhash)
+ 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 revhashsz, i, total_size = daemon->cachesize;
+ struct hostsfile *ah;
+ struct host_record *hr;
+ struct name_list *nl;
+ struct cname *a;
+ struct crec lrec;
+ struct mx_srv_record *mx;
+ struct txt_record *txt;
+ struct interface_name *intr;
+ struct ptr_record *ptr;
+ struct naptr *naptr;
+#ifdef HAVE_DNSSEC
+ struct ds_config *ds;
+#endif
+
+ daemon->metrics[METRIC_DNS_CACHE_INSERTED] = 0;
+ daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED] = 0;
+
+ for (i=0; i<hash_size; i++)
+ for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
+ {
+ cache_blockdata_free(cache);
+
+ tmp = cache->hash_next;
+ if (cache->flags & (F_HOSTS | F_CONFIG))
+ {
+ *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;
+ }
+
+ /* Add locally-configured CNAMEs to the cache */
+ for (a = daemon->cnames; a; a = a->next)
+ if (a->alias[1] != '*' &&
+ ((cache = whine_malloc(SIZEOF_POINTER_CREC))))
+ {
+ cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
+ cache->ttd = a->ttl;
+ cache->name.namep = a->alias;
+ cache->addr.cname.target.name = a->target;
+ cache->addr.cname.is_name_ptr = 1;
+ cache->uid = UID_NONE;
+ cache_hash(cache);
+ make_non_terminals(cache);
+ }
+
+#ifdef HAVE_DNSSEC
+ for (ds = daemon->ds; ds; ds = ds->next)
+ if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) &&
+ (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
+ {
+ cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
+ cache->ttd = daemon->local_ttl;
+ cache->name.namep = ds->name;
+ cache->addr.ds.keylen = ds->digestlen;
+ cache->addr.ds.algo = ds->algo;
+ cache->addr.ds.keytag = ds->keytag;
+ cache->addr.ds.digest = ds->digest_type;
+ cache->uid = ds->class;
+ cache_hash(cache);
+ make_non_terminals(cache);
+ }
+#endif
+
+ /* borrow the packet buffer for a temporary by-address hash */
+ memset(daemon->packet, 0, daemon->packet_buff_sz);
+ revhashsz = daemon->packet_buff_sz / sizeof(struct crec *);
+ /* we overwrote the buffer... */
+ daemon->srv_save = NULL;
+
+ /* Do host_records in config. */
+ for (hr = daemon->host_records; hr; hr = hr->next)
+ for (nl = hr->names; nl; nl = nl->next)
+ {
+ if ((hr->flags & HR_4) &&
+ (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+ {
+ cache->name.namep = nl->name;
+ cache->ttd = hr->ttl;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG;
+ add_hosts_entry(cache, (union all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+ }
+
+ if ((hr->flags & HR_6) &&
+ (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+ {
+ cache->name.namep = nl->name;
+ cache->ttd = hr->ttl;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG;
+ add_hosts_entry(cache, (union all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+ }
+ }
+
+ if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts)
+ {
+ if (daemon->cachesize > 0)
+ my_syslog(LOG_INFO, _("cleared cache"));
+ }
+ else
+ {
+ if (!option_bool(OPT_NO_HOSTS))
+ total_size = read_hostsfile(HOSTSFILE, SRC_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+
+ daemon->addn_hosts = expand_filelist(daemon->addn_hosts);
+ for (ah = daemon->addn_hosts; ah; ah = ah->next)
+ if (!(ah->flags & AH_INACTIVE))
+ total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz);
+ }
+
+ /* Make non-terminal records for all locally-define RRs */
+ lrec.flags = F_FORWARD | F_CONFIG | F_NAMEP | F_IMMORTAL;
+
+ for (txt = daemon->txt; txt; txt = txt->next)
+ {
+ lrec.name.namep = txt->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (naptr = daemon->naptr; naptr; naptr = naptr->next)
+ {
+ lrec.name.namep = naptr->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (mx = daemon->mxnames; mx; mx = mx->next)
+ {
+ lrec.name.namep = mx->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ {
+ lrec.name.namep = intr->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+ {
+ lrec.name.namep = ptr->name;
+ make_non_terminals(&lrec);
+ }
+
+#ifdef HAVE_INOTIFY
+ set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+#endif
+
+}
+
+#ifdef HAVE_DHCP
+struct in_addr a_record_from_hosts(char *name, time_t now)
+{
+ struct crec *crecp = NULL;
+ struct in_addr ret;
+
+ while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4)))
+ if (crecp->flags & F_HOSTS)
+ return crecp->addr.addr4;
+
+ my_syslog(MS_DHCP | LOG_WARNING, _("No IPv4 address found for %s"), name);
+
+ ret.s_addr = 0;
+ return ret;
+}
+
+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, int prot,
+ union all_addr *host_address, time_t ttd)
+{
+ struct crec *crec = NULL, *fail_crec = NULL;
+ unsigned int flags = F_IPV4;
+ int in_hosts = 0;
+ size_t addrlen = sizeof(struct in_addr);
+
+ if (prot == AF_INET6)
+ {
+ flags = F_IPV6;
+ addrlen = sizeof(struct in6_addr);
+ }
+
+ inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN);
+
+ while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME)))
+ {
+ /* check all addresses associated with name */
+ if (crec->flags & (F_HOSTS | F_CONFIG))
+ {
+ if (crec->flags & F_CNAME)
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("%s is a CNAME, not giving it to the DHCP lease of %s"),
+ host_name, daemon->addrbuff);
+ else if (memcmp(&crec->addr, host_address, addrlen) == 0)
+ in_hosts = 1;
+ else
+ fail_crec = crec;
+ }
+ else if (!(crec->flags & F_DHCP))
+ {
+ cache_scan_free(host_name, NULL, C_IN, 0, crec->flags & (flags | F_CNAME | F_FORWARD), NULL, NULL);
+ /* scan_free deletes all addresses associated with name */
+ break;
+ }
+ }
+
+ /* if in hosts, don't need DHCP record */
+ if (in_hosts)
+ return;
+
+ /* Name in hosts, address doesn't match */
+ if (fail_crec)
+ {
+ inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME);
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("not giving name %s to the DHCP lease of %s because "
+ "the name exists in %s with address %s"),
+ host_name, daemon->addrbuff,
+ record_source(fail_crec->uid), daemon->namebuff);
+ return;
+ }
+
+ if ((crec = cache_find_by_addr(NULL, (union all_addr *)host_address, 0, flags)))
+ {
+ if (crec->flags & F_NEG)
+ {
+ flags |= F_REVERSE;
+ cache_scan_free(NULL, (union all_addr *)host_address, C_IN, 0, flags, NULL, NULL);
+ }
+ }
+ else
+ flags |= F_REVERSE;
+
+ if ((crec = dhcp_spare))
+ dhcp_spare = dhcp_spare->next;
+ else /* need new one */
+ crec = whine_malloc(SIZEOF_POINTER_CREC);
+
+ if (crec) /* malloc may fail */
+ {
+ crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
+ if (ttd == 0)
+ crec->flags |= F_IMMORTAL;
+ else
+ crec->ttd = ttd;
+ crec->addr = *host_address;
+ crec->name.namep = host_name;
+ crec->uid = UID_NONE;
+ cache_hash(crec);
+ make_non_terminals(crec);
+ }
+}
+#endif
+
+/* Called when we put a local or DHCP name into the cache.
+ Creates empty cache entries for subnames (ie,
+ for three.two.one, for two.one and one), without
+ F_IPV4 or F_IPV6 or F_CNAME set. These convert
+ NXDOMAIN answers to NoData ones. */
+static void make_non_terminals(struct crec *source)
+{
+ char *name = cache_get_name(source);
+ struct crec *crecp, *tmp, **up;
+ int type = F_HOSTS | F_CONFIG;
+#ifdef HAVE_DHCP
+ if (source->flags & F_DHCP)
+ type = F_DHCP;
+#endif
+
+ /* First delete any empty entries for our new real name. Note that
+ we only delete empty entries deriving from DHCP for a new DHCP-derived
+ entry and vice-versa for HOSTS and CONFIG. This ensures that
+ non-terminals from DHCP go when we reload DHCP and
+ for HOSTS/CONFIG when we re-read. */
+ for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp)
+ {
+ tmp = crecp->hash_next;
+
+ if (!is_outdated_cname_pointer(crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ (crecp->flags & type) &&
+ !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS)) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ {
+ *up = crecp->hash_next;
+#ifdef HAVE_DHCP
+ if (type & F_DHCP)
+ {
+ crecp->next = dhcp_spare;
+ dhcp_spare = crecp;
+ }
+ else
+#endif
+ free(crecp);
+ break;
+ }
+ else
+ up = &crecp->hash_next;
+ }
+
+ while ((name = strchr(name, '.')))
+ {
+ name++;
+
+ /* Look for one existing, don't need another */
+ for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+ if (!is_outdated_cname_pointer(crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ (crecp->flags & type) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ break;
+
+ if (crecp)
+ {
+ /* If the new name expires later, transfer that time to
+ empty non-terminal entry. */
+ if (!(crecp->flags & F_IMMORTAL))
+ {
+ if (source->flags & F_IMMORTAL)
+ crecp->flags |= F_IMMORTAL;
+ else if (difftime(crecp->ttd, source->ttd) < 0)
+ crecp->ttd = source->ttd;
+ }
+ continue;
+ }
+
+#ifdef HAVE_DHCP
+ if ((source->flags & F_DHCP) && dhcp_spare)
+ {
+ crecp = dhcp_spare;
+ dhcp_spare = dhcp_spare->next;
+ }
+ else
+#endif
+ crecp = whine_malloc(SIZEOF_POINTER_CREC);
+
+ if (crecp)
+ {
+ crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_REVERSE);
+ crecp->ttd = source->ttd;
+ crecp->name.namep = name;
+
+ cache_hash(crecp);
+ }
+ }
+}
+
+#ifndef NO_ID
+int cache_make_stat(struct txt_record *t)
+{
+ static char *buff = NULL;
+ static int bufflen = 60;
+ int len;
+ struct server *serv, *serv1;
+ char *p;
+
+ if (!buff && !(buff = whine_malloc(60)))
+ return 0;
+
+ p = buff;
+
+ switch (t->stat)
+ {
+ case TXT_STAT_CACHESIZE:
+ sprintf(buff+1, "%d", daemon->cachesize);
+ break;
+
+ case TXT_STAT_INSERTS:
+ sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+ break;
+
+ case TXT_STAT_EVICTIONS:
+ sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]);
+ break;
+
+ case TXT_STAT_MISSES:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]);
+ break;
+
+ case TXT_STAT_HITS:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+ break;
+
+#ifdef HAVE_AUTH
+ case TXT_STAT_AUTH:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+ break;
+#endif
+
+ case TXT_STAT_SERVERS:
+ /* sum counts from different records for same server */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ serv->flags &= ~SERV_MARK;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags & SERV_MARK))
+ {
+ char *new, *lenp;
+ int port, newlen, bytes_avail, bytes_needed;
+ unsigned int queries = 0, failed_queries = 0;
+ for (serv1 = serv; serv1; serv1 = serv1->next)
+ if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+ {
+ serv1->flags |= SERV_MARK;
+ queries += serv1->queries;
+ failed_queries += serv1->failed_queries;
+ }
+ port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+ lenp = p++; /* length */
+ bytes_avail = bufflen - (p - buff );
+ bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+ if (bytes_needed >= bytes_avail)
+ {
+ /* expand buffer if necessary */
+ newlen = bytes_needed + 1 + bufflen - bytes_avail;
+ if (!(new = whine_malloc(newlen)))
+ return 0;
+ memcpy(new, buff, bufflen);
+ free(buff);
+ p = new + (p - buff);
+ lenp = p - 1;
+ buff = new;
+ bufflen = newlen;
+ bytes_avail = bufflen - (p - buff );
+ bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+ }
+ *lenp = bytes_needed;
+ p += bytes_needed;
+ }
+ t->txt = (unsigned char *)buff;
+ t->len = p - buff;
+
+ return 1;
+ }
+
+ len = strlen(buff+1);
+ t->txt = (unsigned char *)buff;
+ t->len = len + 1;
+ *buff = len;
+ return 1;
+}
+#endif
+
+/* There can be names in the cache containing control chars, don't
+ mess up logging or open security holes. */
+static char *sanitise(char *name)
+{
+ unsigned char *r;
+ if (name)
+ for (r = (unsigned char *)name; *r; r++)
+ if (!isprint((int)*r))
+ return "<name unprintable>";
+
+ return name;
+}
+
+
+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, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+ my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"),
+ daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+#ifdef HAVE_AUTH
+ my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+#endif
+
+ blockdata_report();
+
+ /* sum counts from different records for same server */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ serv->flags &= ~SERV_MARK;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags & SERV_MARK))
+ {
+ int port;
+ unsigned int queries = 0, failed_queries = 0;
+ for (serv1 = serv; serv1; serv1 = serv1->next)
+ if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+ {
+ serv1->flags |= SERV_MARK;
+ queries += serv1->queries;
+ failed_queries += serv1->failed_queries;
+ }
+ port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+ my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), daemon->addrbuff, port, queries, failed_queries);
+ }
+
+ if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG))
+ {
+ struct crec *cache ;
+ int i;
+ my_syslog(LOG_INFO, "Host Address Flags Expires");
+
+ for (i=0; i<hash_size; i++)
+ for (cache = hash_table[i]; cache; cache = cache->hash_next)
+ {
+ char *t = " ";
+ char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
+ *a = 0;
+ if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
+ n = "<Root>";
+ p += sprintf(p, "%-30.30s ", sanitise(n));
+ if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+ a = sanitise(cache_get_cname_target(cache));
+ else if ((cache->flags & F_SRV) && !(cache->flags & F_NEG))
+ {
+ int targetlen = cache->addr.srv.targetlen;
+ ssize_t len = sprintf(a, "%u %u %u ", cache->addr.srv.priority,
+ cache->addr.srv.weight, cache->addr.srv.srvport);
+
+ if (targetlen > (40 - len))
+ targetlen = 40 - len;
+ blockdata_retrieve(cache->addr.srv.target, targetlen, a + len);
+ a[len + targetlen] = 0;
+ }
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ {
+ if (!(cache->flags & F_NEG))
+ sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+ cache->addr.ds.algo, cache->addr.ds.digest);
+ }
+ else if (cache->flags & F_DNSKEY)
+ sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+ cache->addr.key.algo, cache->addr.key.flags);
+#endif
+ else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
+ {
+ a = daemon->addrbuff;
+ if (cache->flags & F_IPV4)
+ inet_ntop(AF_INET, &cache->addr, a, ADDRSTRLEN);
+ else if (cache->flags & F_IPV6)
+ inet_ntop(AF_INET6, &cache->addr, a, ADDRSTRLEN);
+ }
+
+ if (cache->flags & F_IPV4)
+ t = "4";
+ else if (cache->flags & F_IPV6)
+ t = "6";
+ else if (cache->flags & F_CNAME)
+ t = "C";
+ else if (cache->flags & F_SRV)
+ t = "V";
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ t = "S";
+ else if (cache->flags & F_DNSKEY)
+ t = "K";
+#endif
+ p += sprintf(p, "%-40.40s %s%s%s%s%s%s%s%s%s ", a, t,
+ 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" : " ",
+ cache->flags & F_DNSSECOK ? "V" : " ");
+#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_INFO, "%s", daemon->namebuff);
+ }
+ }
+}
+
+char *record_source(unsigned int index)
+{
+ struct hostsfile *ah;
+
+ if (index == SRC_CONFIG)
+ return "config";
+ else if (index == SRC_HOSTS)
+ return HOSTSFILE;
+
+ for (ah = daemon->addn_hosts; ah; ah = ah->next)
+ if (ah->index == index)
+ return ah->fname;
+
+#ifdef HAVE_INOTIFY
+ for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+ if (ah->index == index)
+ return ah->fname;
+#endif
+
+ return "<unknown>";
+}
+
+char *querystr(char *desc, unsigned short type)
+{
+ unsigned int i;
+ int len = 10; /* strlen("type=xxxxx") */
+ const char *types = NULL;
+ static char *buff = NULL;
+ static int bufflen = 0;
+
+ for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
+ if (typestr[i].type == type)
+ {
+ types = typestr[i].name;
+ len = strlen(types);
+ break;
+ }
+
+ if (desc)
+ {
+ len += 2; /* braces */
+ len += strlen(desc);
+ }
+ len++; /* terminator */
+
+ if (!buff || bufflen < len)
+ {
+ if (buff)
+ free(buff);
+ else if (len < 20)
+ len = 20;
+
+ buff = whine_malloc(len);
+ bufflen = len;
+ }
+
+ if (buff)
+ {
+ if (desc)
+ {
+ if (types)
+ sprintf(buff, "%s[%s]", desc, types);
+ else
+ sprintf(buff, "%s[type=%d]", desc, type);
+ }
+ else
+ {
+ if (types)
+ sprintf(buff, "<%s>", types);
+ else
+ sprintf(buff, "<type=%d>", type);
+ }
+ }
+
+ return buff ? buff : "";
+}
+
+static char *edestr(int ede)
+{
+ switch (ede)
+ {
+ case EDE_OTHER: return "other";
+ case EDE_USUPDNSKEY: return "unsupported DNSKEY algorithm";
+ case EDE_USUPDS: return "unsupported DS digest";
+ case EDE_STALE: return "stale answer";
+ case EDE_FORGED: return "forged";
+ case EDE_DNSSEC_IND: return "DNSSEC indeterminate";
+ case EDE_DNSSEC_BOGUS: return "DNSSEC bogus";
+ case EDE_SIG_EXP: return "DNSSEC signature expired";
+ case EDE_SIG_NYV: return "DNSSEC sig not yet valid";
+ case EDE_NO_DNSKEY: return "DNSKEY missing";
+ case EDE_NO_RRSIG: return "RRSIG missing";
+ case EDE_NO_ZONEKEY: return "no zone key bit set";
+ case EDE_NO_NSEC: return "NSEC(3) missing";
+ case EDE_CACHED_ERR: return "cached error";
+ case EDE_NOT_READY: return "not ready";
+ case EDE_BLOCKED: return "blocked";
+ case EDE_CENSORED: return "censored";
+ case EDE_FILTERED: return "filtered";
+ case EDE_PROHIBITED: return "prohibited";
+ case EDE_STALE_NXD: return "stale NXDOMAIN";
+ case EDE_NOT_AUTH: return "not authoritative";
+ case EDE_NOT_SUP: return "not supported";
+ case EDE_NO_AUTH: return "no reachable authority";
+ case EDE_NETERR: return "network error";
+ case EDE_INVALID_DATA: return "invalid data";
+ default: return "unknown";
+ }
+}
+
+void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
+{
+ char *source, *dest = arg;
+ char *verb = "is";
+ char *extra = "";
+
+ if (!option_bool(OPT_LOG))
+ return;
+
+#ifdef HAVE_DNSSEC
+ if ((flags & F_DNSSECOK) && option_bool(OPT_EXTRALOG))
+ extra = " (DNSSEC signed)";
+#endif
+
+ name = sanitise(name);
+
+ if (addr)
+ {
+ dest = daemon->addrbuff;
+
+ if (flags & F_KEYTAG)
+ sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
+ else if (flags & F_RCODE)
+ {
+ unsigned int rcode = addr->log.rcode;
+
+ if (rcode == SERVFAIL)
+ dest = "SERVFAIL";
+ else if (rcode == REFUSED)
+ dest = "REFUSED";
+ else if (rcode == NOTIMP)
+ dest = "not implemented";
+ else
+ sprintf(daemon->addrbuff, "%u", rcode);
+
+ if (addr->log.ede != EDE_UNSET)
+ {
+ extra = daemon->addrbuff;
+ sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+ }
+ }
+ else if (flags & (F_IPV4 | F_IPV6))
+ inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
+ addr, daemon->addrbuff, ADDRSTRLEN);
+ else
+ dest = arg;
+ }
+
+ if (flags & F_REVERSE)
+ {
+ dest = name;
+ name = daemon->addrbuff;
+ }
+
+ if (flags & F_NEG)
+ {
+ if (flags & F_NXDOMAIN)
+ 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)
+ dest = "<CNAME>";
+ else if (flags & F_SRV)
+ dest = "<SRV>";
+ else if (flags & F_RRNAME)
+ dest = arg;
+
+ 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_SECSTAT)
+ {
+ if (addr && addr->log.ede != EDE_UNSET && option_bool(OPT_EXTRALOG))
+ {
+ extra = daemon->addrbuff;
+ sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+ }
+ source = "validation";
+ dest = arg;
+ }
+ else if (flags & F_AUTH)
+ source = "auth";
+ else if (flags & F_SERVER)
+ {
+ source = "forwarded";
+ verb = "to";
+ }
+ else if (flags & F_QUERY)
+ {
+ source = arg;
+ verb = "from";
+ }
+ else if (flags & F_DNSSEC)
+ {
+ source = arg;
+ verb = "to";
+ }
+ else if (flags & F_IPSET)
+ {
+ source = "ipset add";
+ dest = name;
+ name = arg;
+ verb = daemon->addrbuff;
+ }
+ else
+ source = "cached";
+
+ if (strlen(name) == 0)
+ name = ".";
+
+ if (option_bool(OPT_EXTRALOG))
+ {
+ if (flags & F_NOEXTRA)
+ my_syslog(LOG_INFO, "%u %s %s %s %s%s", daemon->log_display_id, source, name, verb, dest, extra);
+ else
+ {
+ int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
+ my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest, extra);
+ }
+ }
+ else
+ my_syslog(LOG_INFO, "%s %s %s %s%s", source, name, verb, dest, extra);
+}