diff --git a/CMakeLists.txt b/CMakeLists.txt index f9233c5ed..c30aa1461 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,6 @@ set(CMAKE_C_STANDARD 17) project(PIHOLE_FTL C) -set(DNSMASQ_VERSION pi-hole-v2.92.2) +set(DNSMASQ_VERSION pi-hole-v2.93rc1) add_subdirectory(src) diff --git a/build.sh b/build.sh index 34ea13493..a07a27405 100755 --- a/build.sh +++ b/build.sh @@ -95,6 +95,16 @@ if [[ -n "${debug}" ]]; then restart=1 fi +# If we are building in debug mode, ensure CMake is configured for a Debug build +# This appends the cache entry so callers can still pass other -D options. +if [[ -n "${debug}" ]]; then + if [[ -n "${cmake_args}" ]]; then + cmake_args="${cmake_args} -DCMAKE_BUILD_TYPE=Debug" + else + cmake_args="-DCMAKE_BUILD_TYPE=Debug" + fi +fi + # If we are in dev mode, we want to build, install, restart, and tail the logs # by default if [[ -n "${dev}" ]]; then diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95118ab02..78dee7e83 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -327,6 +327,33 @@ find_library(LIBNETTLE NAMES libnettle${LIBRARY_SUFFIX} nettle HINTS /usr/local/ find_library(LIBIDN2 NAMES libidn2${LIBRARY_SUFFIX} idn2) find_library(LIBUNISTRING NAMES libunistring${LIBRARY_SUFFIX} unistring) +# Echo library search results to the console +if(LIBHOGWEED) + message(STATUS "Found libhogweed: ${LIBHOGWEED}") +else() + message(WARNING "libhogweed not found, DNSSEC support will be disabled") +endif() +if(LIBGMP) + message(STATUS "Found libgmp: ${LIBGMP}") +else() + message(WARNING "libgmp not found, DNSSEC support will be disabled") +endif() +if(LIBNETTLE) + message(STATUS "Found libnettle: ${LIBNETTLE}") +else() + message(WARNING "libnettle not found, DNSSEC support will be disabled") +endif() +if(LIBIDN2) + message(STATUS "Found libidn2: ${LIBIDN2}") +else() + message(WARNING "libidn2 not found, IDN support will be disabled") +endif() +if(LIBUNISTRING) + message(STATUS "Found libunistring: ${LIBUNISTRING}") +else() + message(WARNING "libunistring not found, IDN support will be disabled") +endif() + target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN2} ${LIBUNISTRING}) if(LUA_DL STREQUAL "true") diff --git a/src/FTL.h b/src/FTL.h index 180b2bfba..50933a50a 100644 --- a/src/FTL.h +++ b/src/FTL.h @@ -12,6 +12,29 @@ #define __USE_XOPEN #define _GNU_SOURCE +#ifdef __GNUC__ +/* + * Protect system headers from project-local macro redefinitions. + * + * `dnsmasq/dnsmasq.h` (and other third-party headers) define very common + * identifiers like `free`/`strdup` as macros that expand to internal + * wrappers (e.g. `free_real(__func__, __LINE__, (x))`). If such macros are + * active while system headers are included, they can be expanded inside + * libc prototypes and inline functions and produce invalid code, causing + * spurious compiler errors. + * + * To avoid this we push any existing macro definition on a stack, undefine + * the name while including system headers, and restore the original macro + * afterwards with `#pragma pop_macro`. + * + * Note: `#pragma push_macro`/`pop_macro` is a GCC/Clang extension, so we + * guard its use with `#ifdef __GNUC__` for portability. + */ +#pragma push_macro("free") +#pragma push_macro("strdup") +#undef free +#undef strdup +#endif #include // variable argument lists #include @@ -176,7 +199,12 @@ // and report accordingly in the log. This will make debugging FTL crash // caused by insufficient memory or by code bugs (not properly dealing // with NULL pointers) much easier. +#ifdef __GNUC__ +#pragma pop_macro("strdup") +#pragma pop_macro("free") +#endif #undef strdup // strdup() is a macro in itself, it needs special handling +#undef free #define free(ptr) { FTLfree(ptr, __FILE__, __FUNCTION__, __LINE__); ptr = NULL; } #define strdup(str_in) FTLstrdup(str_in, __FILE__, __FUNCTION__, __LINE__) #define calloc(numer_of_elements, element_size) FTLcalloc(numer_of_elements, element_size, __FILE__, __FUNCTION__, __LINE__) diff --git a/src/api/2fa.c b/src/api/2fa.c index 510cf7333..f6a67d24d 100644 --- a/src/api/2fa.c +++ b/src/api/2fa.c @@ -33,8 +33,11 @@ static uint32_t hotp(const uint8_t *key, size_t key_len, const uint64_t counter, // Compute HMAC-SHA1 hmac_sha1_update(&ctx, sizeof(counter_be), (uint8_t*)&counter_be); uint8_t out[SHA1_DIGEST_SIZE]; +#if NETTLE_VERSION_MAJOR >= 4 + hmac_sha1_digest(&ctx, out); +#else hmac_sha1_digest(&ctx, SHA1_DIGEST_SIZE, out); - +#endif // Truncate HMAC-SHA1 for ease of use // RFC 6238 (section 5.3): offset = last nibble of hash const uint8_t offset = out[SHA1_DIGEST_SIZE-1] & 0x0F; diff --git a/src/config/password.c b/src/config/password.c index a5f7035d4..cc9e48842 100644 --- a/src/config/password.c +++ b/src/config/password.c @@ -76,7 +76,11 @@ static char * __attribute__((malloc)) double_sha256_password(const char *passwor strlen(password), (uint8_t*)password); +#if NETTLE_VERSION_MAJOR >= 4 + sha256_digest(&ctx, raw_response); +#else sha256_digest(&ctx, SHA256_DIGEST_SIZE, raw_response); +#endif sha256_raw_to_hex(raw_response, response); // Hash password a second time @@ -85,7 +89,11 @@ static char * __attribute__((malloc)) double_sha256_password(const char *passwor strlen(response), (uint8_t*)response); +#if NETTLE_VERSION_MAJOR >= 4 + sha256_digest(&ctx, raw_response); +#else sha256_digest(&ctx, SHA256_DIGEST_SIZE, raw_response); +#endif sha256_raw_to_hex(raw_response, response); return strdup(response); diff --git a/src/dnsmasq/arp.c b/src/dnsmasq/arp.c index a74c2cdc2..0f548933c 100644 --- a/src/dnsmasq/arp.c +++ b/src/dnsmasq/arp.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 diff --git a/src/dnsmasq/auth.c b/src/dnsmasq/auth.c index 7c3452231..94c281c18 100644 --- a/src/dnsmasq/auth.c +++ b/src/dnsmasq/auth.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -591,7 +591,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (auth && zone) { char *authname; - int newoffset, offset = 0; + int newoffset = ansp - (unsigned char *)header, offset = 0; if (!subnet) authname = zone->domain; @@ -631,8 +631,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } /* handle NS and SOA in auth section or for explicit queries */ - newoffset = ansp - (unsigned char *)header; - if (((anscount == 0 && !ns) || soa) && + if (((anscount == 0 && !ns) || soa) && add_resource_record(header, limit, &trunc, 0, &ansp, daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll", authname, daemon->authserver, daemon->hostmaster, @@ -650,11 +649,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (anscount != 0 || ns) { struct name_list *secondary; - + /* Only include the machine running dnsmasq if it's acting as an auth server */ if (daemon->authinterface) { - newoffset = ansp - (unsigned char *)header; if (add_resource_record(header, limit, &trunc, -offset, &ansp, daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver)) { @@ -669,9 +667,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (!subnet) for (secondary = daemon->secondary_forward_server; secondary; secondary = secondary->next) - if (add_resource_record(header, limit, &trunc, offset, &ansp, - daemon->auth_ttl, NULL, T_NS, C_IN, "d", secondary->name)) + if (add_resource_record(header, limit, &trunc, -offset, &ansp, + daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, secondary->name)) { + if (offset == 0) + offset = newoffset; if (ns) anscount++; else diff --git a/src/dnsmasq/blockdata.c b/src/dnsmasq/blockdata.c index 668d63ff8..ebaa82faf 100644 --- a/src/dnsmasq/blockdata.c +++ b/src/dnsmasq/blockdata.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -193,20 +193,22 @@ void *blockdata_retrieve(struct blockdata *block, size_t len, void *data) { size_t blen; struct blockdata *b; - uint8_t *new, *d; + uint8_t *d; - static unsigned int buff_len = 0; - static unsigned char *buff = NULL; - if (!data) { + static unsigned int buff_len = 0; + static unsigned char *buff = NULL; + uint8_t *new; + if (len > buff_len) { - if (!(new = whine_malloc(len))) + blen = len + 1024; + if (!(new = whine_realloc(buff, blen))) return NULL; - if (buff) - free(buff); + buff = new; + buff_len = blen; } data = buff; } diff --git a/src/dnsmasq/bpf.c b/src/dnsmasq/bpf.c index dd67735dc..01e4fc913 100644 --- a/src/dnsmasq/bpf.c +++ b/src/dnsmasq/bpf.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -47,24 +47,20 @@ static union all_addr del_addr; #if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) -int arp_enumerate(void *parm, callback_t callback) +static int arp_enumerate_family(int family, void *parm, callback_t callback) { int mib[6]; size_t needed; char *next; struct rt_msghdr *rtm; - struct sockaddr_inarp *sin2; struct sockaddr_dl *sdl; - struct iovec buff; + static struct iovec buff = { NULL, 0 }; int rc; - buff.iov_base = NULL; - buff.iov_len = 0; - mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; - mib[3] = AF_INET; + mib[3] = family; mib[4] = NET_RT_FLAGS; #ifdef RTF_LLINFO mib[5] = RTF_LLINFO; @@ -72,8 +68,8 @@ int arp_enumerate(void *parm, callback_t callback) mib[5] = 0; #endif if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1 || needed == 0) - return 0; - + return 1; /* not a failure: unsupported or empty table */ + while (1) { if (!expand_buf(&buff, needed)) @@ -83,20 +79,50 @@ int arp_enumerate(void *parm, callback_t callback) break; needed += needed / 8; } + if (rc == -1) return 0; for (next = buff.iov_base ; next < (char *)buff.iov_base + needed; next += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)next; - sin2 = (struct sockaddr_inarp *)(rtm + 1); - sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2)); - if (!callback.af_unspec(AF_INET, &sin2->sin_addr, LLADDR(sdl), sdl->sdl_alen, parm)) - return 0; + if (family == AF_INET) + { + struct sockaddr_inarp *sin2 = (struct sockaddr_inarp *)(rtm + 1); + sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2)); + if (!callback.af_unspec(AF_INET, &sin2->sin_addr, + LLADDR(sdl), sdl->sdl_alen, parm)) + return 0; + } + else + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)(rtm + 1); + sdl = (struct sockaddr_dl *)((char *)sin6 + SA_SIZE(sin6)); + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + { + /* PF_ROUTE sysctl returns raw kernel structures with the interface + index embedded in bytes 2-3 of link-local addresses. Extract it + into sin6_scope_id per the KAME API contract before clearing. */ + sin6->sin6_scope_id = + ((uint32_t)(sin6->sin6_addr.s6_addr[2]) << 8) | sin6->sin6_addr.s6_addr[3]; + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } + if (!callback.af_unspec(AF_INET6, &sin6->sin6_addr, + LLADDR(sdl), sdl->sdl_alen, parm)) + return 0; + } } - + return 1; } + +static int arp_enumerate(void *parm, callback_t callback) +{ + if (!arp_enumerate_family(AF_INET, parm, callback)) + return 0; + return arp_enumerate_family(AF_INET6, parm, callback); +} #endif /* defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) */ diff --git a/src/dnsmasq/cache.c b/src/dnsmasq/cache.c index 4f5dacfa9..d4273227a 100644 --- a/src/dnsmasq/cache.c +++ b/src/dnsmasq/cache.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -19,14 +19,18 @@ #include "webserver/webserver.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 *config_spare = NULL; static struct crec *new_chain = NULL; static int insert_error; static union bigname *big_free = NULL; static int bignames_left, hash_size; +struct nameblock { + struct nameblock *next; + unsigned int last, index; + char data[NAMEBLOCK_CHARS]; +} *hostblocks = NULL; + 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); @@ -209,6 +213,64 @@ void cache_init(void) rehash(daemon->cachesize); } +static struct crec *get_config_crec(void) +{ + struct crec *ret; + + if (config_spare) + { + ret = config_spare; + config_spare = config_spare->next; + } + else + ret = whine_malloc(SIZEOF_POINTER_CREC); + + return ret; +} + +static void free_config_crec(struct crec *p) +{ + p->next = config_spare; + config_spare = p; +} + +static char *store_name(unsigned int namelen, unsigned int index) +{ + struct nameblock *block; + char *ret = NULL; + + if (namelen > NAMEBLOCK_CHARS) + return NULL; + + for (block = hostblocks; block; block = block->next) + if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen) + break; + + if (!block && ((block = whine_malloc(sizeof(struct nameblock))))) + { + block->next = hostblocks; + block->index = index; + hostblocks = block; + } + + if (block && NAMEBLOCK_CHARS - block->last >= namelen) + { + ret = &block->data[block->last]; + block->last += namelen; + } + + return ret; +} + +static void free_names(unsigned int index) +{ + struct nameblock *block; + + for (block = hostblocks; block; block = block->next) + if (index == UID_NONE || block->index == index) + block->last = 0; +} + /* 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 @@ -458,12 +520,14 @@ unsigned int cache_remove_uid(const unsigned int uid) if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid) { *up = tmp; - free(crecp); + free_config_crec(crecp); removed++; } else up = &crecp->hash_next; } + + free_names(uid); return removed; } @@ -872,7 +936,7 @@ void cache_end_insert(void) #ifdef HAVE_DNSSEC void cache_update_hwm(void) { - /* Sneak out possibly updated crypto HWM values. */ + /* Sneak out possibly updated crypto HWM values. */ unsigned char op = PIPE_OP_STATS; read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE); @@ -1017,6 +1081,7 @@ int cache_recv_insert(time_t now, int fd) } case PIPE_OP_RESULT: + case PIPE_OP_KILLED: { /* UDP validation moved to TCP to avoid truncation. Restart UDP validation process with the returned result. */ @@ -1030,21 +1095,30 @@ int cache_recv_insert(time_t now, int fd) !read_write(fd, (unsigned char *)&ret_len, sizeof(ret_len), RW_READ) || !read_write(fd, (unsigned char *)daemon->packet, ret_len, RW_READ) || !read_write(fd, (unsigned char *)&forward, sizeof(forward), RW_READ) || - !read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ) || - !read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) || - !read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) || - !read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) || - !read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ)) + !read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ)) + return 0; + + if (op == PIPE_OP_RESULT && + (!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) || + !read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) || + !read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) || + !read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ))) return 0; + + if (op == PIPE_OP_KILLED) + my_syslog(LOG_INFO, _("TCP process for DNSSEC validation timed out")); /* There's a tiny chance that the frec may have been freed and reused before the TCP process returns. Detect that with the uid field which is unique modulo 2^32 for each use. */ if (uid == forward->uid) { - /* repatriate the work counters from the child process. */ - *keycountp = keycount; - *validatecountp = validatecount; + if (op == PIPE_OP_RESULT) + { + /* repatriate the work counters from the child process. */ + *keycountp = keycount; + *validatecountp = validatecount; + } if (!forward->dependent) return_reply(now, forward, (struct dns_header *)daemon->packet, ret_len, status); @@ -1271,7 +1345,7 @@ void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen, while ((lookup = cache_find_by_name(lookup, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6)))) if ((lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0) { - free(cache); + free_config_crec(cache); return; } @@ -1365,7 +1439,7 @@ static int gettok(FILE *f, char *token) return eatspace(f); } - if (count < (MAXDNAME - 1)) + if (count < (MAXDNAMESTR - 1)) { token[count++] = c; token[count] = 0; @@ -1394,13 +1468,13 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr { if (inet_pton(AF_INET, token, &addr) > 0) { - flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; + flags = F_NAMEP | 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; + flags = F_NAMEP | F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6; addrlen = IN6ADDRSZ; domain_suffix = get_domain6(&addr.addr6); } @@ -1434,27 +1508,37 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr 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)))) + if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && (cache = get_config_crec())) { - 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++; - names_done++; + if (!(cache->name.namep = store_name(strlen(canon) + 2 + strlen(domain_suffix), index))) + free_config_crec(cache); + else + { + strcpy(cache->name.namep, canon); + strcat(cache->name.namep, "."); + strcat(cache->name.namep, domain_suffix); + cache->flags = flags; + cache->ttd = daemon->local_ttl; + add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); + name_count++; + names_done++; + } } - if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1))) + if ((cache = get_config_crec())) { - strcpy(cache->name.sname, canon); - cache->flags = flags; - cache->ttd = daemon->local_ttl; - add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); - name_count++; - names_done++; + if (!(cache->name.namep = store_name(strlen(canon) + 1, index))) + free_config_crec(cache); + else + { + strcpy(cache->name.namep, canon); + cache->flags = flags; + cache->ttd = daemon->local_ttl; + add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); + name_count++; + names_done++; + } } + free(canon); } @@ -1505,7 +1589,7 @@ void cache_reload(void) if (cache->flags & (F_HOSTS | F_CONFIG)) { *up = cache->hash_next; - free(cache); + free_config_crec(cache); } else if (!(cache->flags & F_DHCP)) { @@ -1520,11 +1604,13 @@ void cache_reload(void) else up = &cache->hash_next; } + + free_names(UID_NONE); /* free everything */ /* 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 = get_config_crec()))) { cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; cache->ttd = a->ttl; @@ -1538,25 +1624,30 @@ void cache_reload(void) #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))) + if (ds->name && (cache = get_config_crec())) { - cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP; - cache->ttd = daemon->local_ttl; - cache->name.namep = ds->name; - cache->uid = ds->class; - if (ds->digestlen != 0) + + if (!(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen))) + free_config_crec(cache); + else { - 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->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP; + cache->ttd = daemon->local_ttl; + cache->name.namep = ds->name; + cache->uid = ds->class; + if (ds->digestlen != 0) + { + 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; + } + else + cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR; + + cache_hash(cache); + make_non_terminals(cache); } - else - cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR; - - cache_hash(cache); - make_non_terminals(cache); } #endif @@ -1571,7 +1662,7 @@ void cache_reload(void) for (nl = hr->names; nl; nl = nl->next) { if ((hr->flags & HR_4) && - (cache = whine_malloc(SIZEOF_POINTER_CREC))) + (cache = get_config_crec())) { cache->name.namep = nl->name; cache->ttd = hr->ttl; @@ -1580,7 +1671,7 @@ void cache_reload(void) } if ((hr->flags & HR_6) && - (cache = whine_malloc(SIZEOF_POINTER_CREC))) + (cache = get_config_crec())) { cache->name.namep = nl->name; cache->ttd = hr->ttl; @@ -1672,8 +1763,7 @@ void cache_unhash_dhcp(void) if (cache->flags & F_DHCP) { *up = cache->hash_next; - cache->next = dhcp_spare; - dhcp_spare = cache; + free_config_crec(cache); } else up = &cache->hash_next; @@ -1724,7 +1814,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, /* Name in hosts, address doesn't match */ if (fail_crec) { - inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME); + inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAMESTR); 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"), @@ -1744,12 +1834,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, 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 */ + if ((crec = get_config_crec())) { crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD; if (ttd == 0) @@ -1796,15 +1881,7 @@ static void make_non_terminals(struct crec *source) 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); + free_config_crec(crecp); break; } else @@ -1837,17 +1914,7 @@ static void make_non_terminals(struct crec *source) 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) + if ((crecp = get_config_crec())) { crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_RR | F_DNSKEY | F_DS | F_REVERSE); if (!(crecp->flags & F_IMMORTAL)) @@ -2276,9 +2343,9 @@ 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; - + static struct iovec buff = { NULL, 0 }; + char *buffp; + for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++) if (typestr[i].type == type) { @@ -2293,37 +2360,28 @@ char *querystr(char *desc, unsigned short type) len += strlen(desc); } len++; /* terminator */ + + if (!expand_buf(&buff, len)) + return ""; + + buffp = buff.iov_base; - if (!buff || bufflen < len) + if (desc) { - if (buff) - free(buff); - else if (len < 20) - len = 20; - - buff = whine_malloc(len); - bufflen = len; + if (types) + sprintf(buffp, "%s[%s]", desc, types); + else + sprintf(buffp, "%s[type=%d]", desc, type); } - - if (buff) + else { - if (desc) - { - if (types) - sprintf(buff, "%s[%s]", desc, types); - else - sprintf(buff, "%s[type=%d]", desc, type); - } + if (types) + sprintf(buffp, "<%s>", types); else - { - if (types) - sprintf(buff, "<%s>", types); - else - sprintf(buff, "", type); - } + sprintf(buffp, "", type); } - return buff ? buff : ""; + return buffp; } /**** Pi-hole modified: removed static and added prototype to dnsmasq.h ****/ @@ -2361,10 +2419,20 @@ const char *edestr(int ede) case EDE_UNS_NS3_ITER: return "unsupported NSEC3 iterations value"; case EDE_UNABLE_POLICY: return "uanble to conform to policy"; case EDE_SYNTHESIZED: return "synthesized"; + case EDE_US_SERVFAIL: return "upstream returned SERVFAIL"; default: return "unknown"; } } +static int error_occured(unsigned int flags) { + if (flags & F_RCODE) + return 1; + else if (flags & F_NEG) + return 1; + else + return 0; +} + /**** P-hole modified: Added file and line and serve log_query via macro defined in dnsmasq.h ****/ void _log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, unsigned short type, const char *file, const int line) { @@ -2380,12 +2448,15 @@ void _log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, if (!option_bool(OPT_LOG)) return; + if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occured(flags)) + return; + /* F_NOERR is reused here to indicate logs arrising from auth queries */ if (!(flags & F_NOERR) && option_bool(OPT_AUTH_LOG)) return; /* build query type string if requested */ - if (!(flags & (F_SERVER | F_IPSET | F_QUERY)) && type > 0) + if (!(flags & (F_SERVER | F_IPSET | F_QUERY | F_KEYTAG | F_RR)) && type > 0) arg = querystr(arg, type); dest = arg; @@ -2401,19 +2472,25 @@ void _log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, { dest = daemon->addrbuff; - if (flags & F_RR) - { - if (flags & F_KEYTAG) - dest = querystr(NULL, addr->rrblock.rrtype); - else - dest = querystr(NULL, addr->rrdata.rrtype); - } - else if (flags & F_KEYTAG) - sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest); + if (flags & F_RR) + { + if (flags & F_KEYTAG) + dest = querystr(NULL, addr->rrblock.rrtype); + else + dest = querystr(NULL, addr->rrdata.rrtype); + } +#ifdef HAVE_DNSSEC + else if (flags & F_KEYTAG) + { + snprintf(daemon->addrbuff, ADDRSTRLEN, arg, addr->log.keytag, addr->log.algo, addr->log.digest); + if (type) + extra = " (not supported)"; + } +#endif else if (flags & F_RCODE) { unsigned int rcode = addr->log.rcode; - + if (rcode == SERVFAIL) dest = "SERVFAIL"; else if (rcode == REFUSED) diff --git a/src/dnsmasq/config.h b/src/dnsmasq/config.h index 5361667be..79fa2ec1d 100644 --- a/src/dnsmasq/config.h +++ b/src/dnsmasq/config.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -22,6 +22,7 @@ #define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */ #define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */ #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ +#define NAMEBLOCK_CHARS 1500 /* quantum of memory allocation for names from /etc/hosts */ #define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */ #define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */ #define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */ @@ -42,7 +43,7 @@ #define PING_CACHE_TIME 30 /* Ping test assumed to be valid this long. */ #define DECLINE_BACKOFF 600 /* disable DECLINEd static addresses for this long */ #define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */ -#define SMALLDNAME 50 /* most domain names are smaller than this */ +#define SMALLDNAME 75 /* most domain names are smaller than this */ #define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */ #define DNSSEC_MIN_TTL 60 /* DNSKEY and DS records in cache last at least this long */ #define HOSTSFILE "/etc/hosts" @@ -144,7 +145,8 @@ HAVE_LOOP include functionality to probe for and remove DNS forwarding loops. HAVE_INOTIFY - use the Linux inotify facility to efficiently re-read configuration files. + use the Linux and FreeBSD >= 15 inotify facility + to efficiently re-read configuration files. NO_ID Don't report *.bind CHAOS info to clients, forward such requests upstream instead. @@ -388,8 +390,15 @@ HAVE_SOCKADDR_SA_LEN #undef HAVE_DUMPFILE #endif -#if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY) -#define HAVE_INOTIFY +#if !defined(NO_INOTIFY) +# if defined (HAVE_LINUX_NETWORK) +# define HAVE_INOTIFY +# elif defined (__FreeBSD__) && __FreeBSD__ + 0 >= 15 +# include +# if __FreeBSD_version >= 1500068 /* 15.0.0 */ +# define HAVE_INOTIFY +# endif +# endif #endif /* This never compiles code, it's only used by the makefile to fingerprint builds. */ diff --git a/src/dnsmasq/conntrack.c b/src/dnsmasq/conntrack.c index 24935041d..0c6d51abe 100644 --- a/src/dnsmasq/conntrack.c +++ b/src/dnsmasq/conntrack.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 diff --git a/src/dnsmasq/crypto.c b/src/dnsmasq/crypto.c index 6a5f0c103..cb7131946 100644 --- a/src/dnsmasq/crypto.c +++ b/src/dnsmasq/crypto.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -93,7 +93,15 @@ static void null_hash_update(void *ctxv, size_t length, const uint8_t *src) memcpy(null_hash_buff + ctx->len, src, length); ctx->len += length; } - + +/* The prototype changes in nettle 4.0 to omit the length argument */ +#if MIN_VERSION(4, 0) +static void null_hash_digest(void *ctx, uint8_t *dst) +{ + ((struct null_hash_digest *)dst)->buff = null_hash_buff; + ((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len; +} +#else static void null_hash_digest(void *ctx, size_t length, uint8_t *dst) { (void)length; @@ -101,6 +109,7 @@ static void null_hash_digest(void *ctx, size_t length, uint8_t *dst) ((struct null_hash_digest *)dst)->buff = null_hash_buff; ((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len; } +#endif static struct nettle_hash null_hash = { "null_hash", @@ -126,20 +135,18 @@ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **diges if (ctx_sz < hash->context_size) { - if (!(new = whine_malloc(hash->context_size))) + if (!(new = whine_realloc(ctx, hash->context_size))) return 0; - if (ctx) - free(ctx); + ctx = new; ctx_sz = hash->context_size; } if (digest_sz < hash->digest_size) { - if (!(new = whine_malloc(hash->digest_size))) + if (!(new = whine_realloc(digest, hash->digest_size))) return 0; - if (digest) - free(digest); + digest = new; digest_sz = hash->digest_size; } @@ -503,4 +510,16 @@ const struct nettle_hash *hash_find(char *name) #endif } +/* The prototype changes in nettle 4.0 to omit the length argument */ +void nettle_digest_wrapper(const struct nettle_hash *hash, void *ctx, size_t length, uint8_t *dst) +{ +#if MIN_VERSION(4, 0) + (void)length; + hash->digest(ctx, dst); +#else + hash->digest(ctx, length, dst); +#endif +} + + #endif /* defined(HAVE_DNSSEC) */ diff --git a/src/dnsmasq/dbus.c b/src/dnsmasq/dbus.c index 0e2243f38..aa59eb82e 100644 --- a/src/dnsmasq/dbus.c +++ b/src/dnsmasq/dbus.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -736,7 +736,7 @@ static void add_dict_entry(DBusMessageIter *container, const char *key, const ch static void add_dict_int(DBusMessageIter *container, const char *key, const unsigned int val) { - snprintf(daemon->namebuff, MAXDNAME, "%u", val); + snprintf(daemon->namebuff, MAXDNAMESTR, "%u", val); add_dict_entry(container, key, daemon->namebuff); } diff --git a/src/dnsmasq/dhcp-common.c b/src/dnsmasq/dhcp-common.c index 192d37439..7e794e821 100644 --- a/src/dnsmasq/dhcp-common.c +++ b/src/dnsmasq/dhcp-common.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -278,9 +278,9 @@ void log_tags(struct dhcp_netid *netid, u32 xid) if (!n) { - strncat (s, netid->net, (MAXDNAME-1) - strlen(s)); + strncat (s, netid->net, MAXDNAMESTR - strlen(s)); if (netid->next) - strncat (s, ", ", (MAXDNAME-1) - strlen(s)); + strncat (s, ", ", MAXDNAMESTR - strlen(s)); } } my_syslog(MS_DHCP | LOG_INFO, _("%u tags: %s"), xid, s); @@ -882,7 +882,7 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, buf[j++] = c; } i = l; - if (val[i] != 0 && j < buf_len) + if (i < opt_len && val[i] != 0 && j < buf_len) buf[j++] = '.'; } } @@ -892,24 +892,23 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, unsigned char *p; i = 0, j = 0; - while (1) + while (i + 2 <= opt_len) { p = &val[i]; GETSHORT(len, p); + if (i + 2 + len > opt_len) + break; /* malformed: body extends beyond option */ for (k = 0; k < len && j < buf_len; k++) { char c = *p++; if (isprint((unsigned char)c)) buf[j++] = c; } - i += len +2; - if (i >= opt_len) - break; - - if (j < buf_len) + i += len + 2; + if (i < opt_len && j < buf_len) buf[j++] = ','; } - } + } #endif else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0) { diff --git a/src/dnsmasq/dhcp-protocol.h b/src/dnsmasq/dhcp-protocol.h index 72c420d97..adf8d08be 100644 --- a/src/dnsmasq/dhcp-protocol.h +++ b/src/dnsmasq/dhcp-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 diff --git a/src/dnsmasq/dhcp.c b/src/dnsmasq/dhcp.c index 4a47d8a6a..cb87a301a 100644 --- a/src/dnsmasq/dhcp.c +++ b/src/dnsmasq/dhcp.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -399,7 +399,7 @@ void dhcp_packet(time_t now, int pxe_fd) if (mess->ciaddr.s_addr != 0) dest.sin_addr = mess->ciaddr; } - if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply) + else if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply) { /* Send to BOOTP relay. */ if (is_relay_use_source) @@ -958,7 +958,7 @@ void dhcp_read_ethers(void) up = &config->next; } - while (fgets(buff, MAXDNAME, f)) + while (fgets(buff, MAXDNAMESTR, f)) { char *host = NULL; diff --git a/src/dnsmasq/dhcp6-protocol.h b/src/dnsmasq/dhcp6-protocol.h index ad5183de8..d55f1475e 100644 --- a/src/dnsmasq/dhcp6-protocol.h +++ b/src/dnsmasq/dhcp6-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 diff --git a/src/dnsmasq/dhcp6.c b/src/dnsmasq/dhcp6.c index 480ecebfd..3b81e5acb 100644 --- a/src/dnsmasq/dhcp6.c +++ b/src/dnsmasq/dhcp6.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 diff --git a/src/dnsmasq/dns-protocol.h b/src/dnsmasq/dns-protocol.h index 0db5913f2..43dddcfa0 100644 --- a/src/dnsmasq/dns-protocol.h +++ b/src/dnsmasq/dns-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2026 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 @@ -22,8 +22,34 @@ #define IN6ADDRSZ 16 #define INADDRSZ 4 +/* Dnsmasq handles domains names internally as NULL-terminated C strings. + The '.' characters in these strings are significant as label-seperators. + '.' and /0 characters _within_ labels are escaped and take up two + characters to allow labels to contain arbitrary characters. + + The maximum length of a domain name in wire format is 255 bytes. + and the maximum length of this when coverted to a