diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e96c27a..98f06b73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,6 @@ cmake_minimum_required(VERSION 2.8.12) project(PIHOLE_FTL C) -set(DNSMASQ_VERSION pi-hole-v2.89-e1de9c2) +set(DNSMASQ_VERSION pi-hole-v2.90) add_subdirectory(src) diff --git a/src/api/docs/content/specs/auth.yaml b/src/api/docs/content/specs/auth.yaml index dabe9e5d..8ea10a13 100644 --- a/src/api/docs/content/specs/auth.yaml +++ b/src/api/docs/content/specs/auth.yaml @@ -244,7 +244,10 @@ components: - Authentication operationId: "add_app" description: | - Create a new application password. The generated password is shown only once and cannot be retrieved later so make sure to store it in a safe place. The application password can be used to authenticate against the API instead of the regular password. It does not require 2FA verification. Generating a new application password will invalidate all currently active sessions. + Create a new application password. The generated password is shown only once and cannot be retrieved later - make sure to store it in a safe place. The application password can be used to authenticate against the API instead of the regular password. + It does not require 2FA verification. Generating a new application password will invalidate all currently active sessions. + + Note that this endpoint only generates an application password accompanied by its hash. To make this new password effective, the returned `hash` has to be set as `webserver.api.app_password` in the Pi-hole configuration in a follow-up step. This can be done in various ways, e.g. via the API (`PATCH /api/config/webserver/api/app_pwhash`), the graphical web interface (Settings -> All Settings) or by editing the configuration file directly. responses: '200': description: OK diff --git a/src/api/docs/content/specs/info.yaml b/src/api/docs/content/specs/info.yaml index b38ee605..dadaee27 100644 --- a/src/api/docs/content/specs/info.yaml +++ b/src/api/docs/content/specs/info.yaml @@ -752,6 +752,115 @@ components: type: boolean description: Whether or not FTL is allowed to perform destructive actions example: true + dnsmasq: + type: object + description: Metrics from the embedded dnsmasq resolver + properties: + dns_cache_inserted: + type: integer + description: Number of inserted entries in DNS cache + example: 8 + dns_cache_live_freed: + type: integer + description: Number of freed live entries in DNS cache + example: 0 + dns_queries_forwarded: + type: integer + description: Number of forwarded DNS queries + example: 2 + dns_auth_answered: + type: integer + description: Number of DNS queries for authoritative zones + example: 0 + dns_local_answered: + type: integer + description: Number of DNS queries answered from local cache + example: 74 + dns_stale_answered: + type: integer + description: Number of DNS queries answered from local cache (stale entries) + example: 0 + dns_unanswered: + type: integer + description: Number of unanswered DNS queries + example: 0 + bootp: + type: integer + description: Number of BOOTP requests + example: 0 + pxe: + type: integer + description: Number of PXE requests + example: 0 + dhcp_ack: + type: integer + description: Number of DHCP ACK + example: 0 + dhcp_decline: + type: integer + description: Number of DHCP DECLINE + example: 0 + dhcp_discover: + type: integer + description: Number of DHCP DISCOVER + example: 0 + dhcp_inform: + type: integer + description: Number of DHCP INFORM + example: 0 + dhcp_nak: + type: integer + description: Number of DHCP NAK + example: 0 + dhcp_offer: + type: integer + description: Number of DHCP OFFER + example: 0 + dhcp_release: + type: integer + description: Number of DHCP RELEASE + example: 0 + dhcp_request: + type: integer + description: Number of DHCP REQUEST + example: 0 + noanswer: + type: integer + description: Number of DHCP requests without answer (rapid commit) + example: 0 + leases_allocated_4: + type: integer + description: Number of allocated IPv4 leases + example: 0 + leases_pruned_4: + type: integer + description: Number of pruned IPv4 leases + example: 0 + leases_allocated_6: + type: integer + description: Number of allocated IPv6 leases + example: 0 + leases_pruned_6: + type: integer + description: Number of pruned IPv6 leases + example: 0 + tcp_connections: + type: integer + description: Number of dedicated TCP workers + example: 0 + dnssec_max_crypto_use: + type: integer + description: DNSSEC per-query crypto work HWM + example: 0 + dnssec_max_sig_fail: + type: integer + description: DNSSEC per-RRSet signature fails HWM + example: 0 + dnssec_max_work: + type: integer + description: DNSSEC per-query subqueries HWM + example: 0 + database: type: object properties: diff --git a/src/api/info.c b/src/api/info.c index 8be86829..bf8b6f3b 100644 --- a/src/api/info.c +++ b/src/api/info.c @@ -590,6 +590,11 @@ static int get_ftl_obj(struct ftl_conn *api, cJSON *ftl) JSON_ADD_BOOL_TO_OBJECT(ftl, "allow_destructive", config.webserver.api.allow_destructive.v.b); + // dnsmasq struct + cJSON *dnsmasq = JSON_NEW_OBJECT(); + get_dnsmasq_metrics_obj(dnsmasq); + JSON_ADD_ITEM_TO_OBJECT(ftl, "dnsmasq", dnsmasq); + // All okay return 0; } diff --git a/src/config/dnsmasq_config.c b/src/config/dnsmasq_config.c index e622d964..159f34b9 100644 --- a/src/config/dnsmasq_config.c +++ b/src/config/dnsmasq_config.c @@ -658,6 +658,15 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_ } } + // Add ANY filtering + fputs("# RFC 8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY\n", pihole_conf); + fputs("# Filters replies to queries for type ANY. Everything other than A, AAAA, MX and CNAME\n", pihole_conf); + fputs("# records are removed. Since ANY queries with forged source addresses can be used in DNS amplification attacks\n", pihole_conf); + fputs("# replies to ANY queries can be large) this defangs such attacks, whilst still supporting the\n", pihole_conf); + fputs("# one remaining possible use of ANY queries. See RFC 8482 para 4.3 for details.\n", pihole_conf); + fputs("filter-rr=ANY\n", pihole_conf); + fputs("\n", pihole_conf); + // Add additional config lines to disk (if present) if(conf->misc.dnsmasq_lines.v.json != NULL && cJSON_GetArraySize(conf->misc.dnsmasq_lines.v.json) > 0) diff --git a/src/dnsmasq/arp.c b/src/dnsmasq/arp.c index 1676e916..0a5a9bfa 100644 --- a/src/dnsmasq/arp.c +++ b/src/dnsmasq/arp.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 1fb03254..e6adc379 100644 --- a/src/dnsmasq/auth.c +++ b/src/dnsmasq/auth.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/blockdata.c b/src/dnsmasq/blockdata.c index 444a03a6..96698760 100644 --- a/src/dnsmasq/blockdata.c +++ b/src/dnsmasq/blockdata.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/bpf.c b/src/dnsmasq/bpf.c index 15a9b093..62b589c2 100644 --- a/src/dnsmasq/bpf.c +++ b/src/dnsmasq/bpf.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/cache.c b/src/dnsmasq/cache.c index d2a50185..eba13d24 100644 --- a/src/dnsmasq/cache.c +++ b/src/dnsmasq/cache.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -126,6 +126,7 @@ static const struct { { 258, "AVC" }, /* Application Visibility and Control [Wolfgang_Riedel] AVC/avc-completed-template 2016-02-26*/ { 259, "DOA" }, /* Digital Object Architecture [draft-durand-doa-over-dns] DOA/doa-completed-template 2017-08-30*/ { 260, "AMTRELAY" }, /* Automatic Multicast Tunneling Relay [RFC8777] AMTRELAY/amtrelay-completed-template 2019-02-06*/ + { 261, "RESINFO" }, /* Resolver Information as Key/Value Pairs https://datatracker.ietf.org/doc/draft-ietf-add-resolver-info/06/ */ { 32768, "TA" }, /* DNSSEC Trust Authorities [Sam_Weiler][http://cameo.library.cmu.edu/][ Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.] 2005-12-13*/ { 32769, "DLV" }, /* DNSSEC Lookaside Validation (OBSOLETE) [RFC8749][RFC4431] */ }; @@ -441,18 +442,21 @@ unsigned int cache_remove_uid(const unsigned int uid) { int i; unsigned int removed = 0; - struct crec *crecp, **up; + struct crec *crecp, *tmp, **up; for (i = 0; i < hash_size; i++) - for (crecp = hash_table[i], up = &hash_table[i]; crecp; crecp = crecp->hash_next) - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid) - { - *up = crecp->hash_next; - free(crecp); - removed++; - } - else - up = &crecp->hash_next; + for (crecp = hash_table[i], up = &hash_table[i]; crecp; crecp = tmp) + { + tmp = crecp->hash_next; + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid) + { + *up = tmp; + free(crecp); + removed++; + } + else + up = &crecp->hash_next; + } return removed; } @@ -815,32 +819,28 @@ void cache_end_insert(void) 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_RR)) + read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0); + + if (flags & F_RR) { - read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0); - - if (flags & F_RR) - { - /* A negative RR entry is possible and has no data, obviously. */ - if (!(flags & F_NEG) && (flags & F_KEYTAG)) - blockdata_write(new_chain->addr.rrblock.rrdata, new_chain->addr.rrblock.datalen, 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 + /* A negative RR entry is possible and has no data, obviously. */ + if (!(flags & F_NEG) && (flags & F_KEYTAG)) + blockdata_write(new_chain->addr.rrblock.rrdata, new_chain->addr.rrblock.datalen, 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 } } @@ -851,7 +851,18 @@ void cache_end_insert(void) if (daemon->pipe_to_parent != -1) { ssize_t m = -1; + read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); + +#ifdef HAVE_DNSSEC + /* Sneak out possibly updated crypto HWM values. */ + m = daemon->metrics[METRIC_CRYPTO_HWM]; + read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); + m = daemon->metrics[METRIC_SIG_FAIL_HWM]; + read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); + m = daemon->metrics[METRIC_WORK_HWM]; + read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); +#endif } new_chain = NULL; @@ -870,7 +881,7 @@ int cache_recv_insert(time_t now, int fd) cache_start_insert(); - while(1) + while (1) { if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1)) @@ -878,13 +889,29 @@ int cache_recv_insert(time_t now, int fd) if (m == -1) { +#ifdef HAVE_DNSSEC + /* Sneak in possibly updated crypto HWM. */ + if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1)) + return 0; + if (m > daemon->metrics[METRIC_CRYPTO_HWM]) + daemon->metrics[METRIC_CRYPTO_HWM] = m; + if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1)) + return 0; + if (m > daemon->metrics[METRIC_SIG_FAIL_HWM]) + daemon->metrics[METRIC_SIG_FAIL_HWM] = m; + if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1)) + return 0; + if (m > daemon->metrics[METRIC_WORK_HWM]) + daemon->metrics[METRIC_WORK_HWM] = m; +#endif 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)) + !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1) || + !read_write(fd, (unsigned char *)&addr, sizeof(addr), 1)) return 0; daemon->namebuff[m] = 0; @@ -915,30 +942,23 @@ int cache_recv_insert(time_t now, int fd) { unsigned short class = C_IN; - if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_RR)) - { - if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1)) - return 0; - - if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG) - && !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen))) - return 0; + if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG) + && !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen))) + 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 + 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); } } @@ -1839,8 +1859,18 @@ static void dump_cache_entry(struct crec *cache, time_t now) p = buff; *a = 0; - if (strlen(n) == 0 && !(cache->flags & F_REVERSE)) - n = ""; + + if (cache->flags & F_REVERSE) + { + if ((cache->flags & F_NEG)) + n = ""; + } + else + { + if (strlen(n) == 0) + n = ""; + } + p += sprintf(p, "%-30.30s ", sanitise(n)); if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache)) a = sanitise(cache_get_cname_target(cache)); @@ -2005,9 +2035,19 @@ void dump_cache(time_t now) #ifdef HAVE_AUTH my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); #endif +#ifdef HAVE_DNSSEC + my_syslog(LOG_INFO, _("DNSSEC per-query subqueries HWM %u"), daemon->metrics[METRIC_WORK_HWM]); + my_syslog(LOG_INFO, _("DNSSEC per-query crypto work HWM %u"), daemon->metrics[METRIC_CRYPTO_HWM]); + my_syslog(LOG_INFO, _("DNSSEC per-RRSet signature fails HWM %u"), daemon->metrics[METRIC_SIG_FAIL_HWM]); +#endif blockdata_report(); - + my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."), + daemon->metrics[METRIC_TCP_CONNECTIONS], + daemon->max_procs_used, + daemon->max_procs); + daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS]; + /* sum counts from different records for same server */ for (serv = daemon->servers; serv; serv = serv->next) serv->flags &= ~SERV_MARK; @@ -2161,6 +2201,11 @@ const char *edestr(int ede) case EDE_NO_AUTH: return "no reachable authority"; case EDE_NETERR: return "network error"; case EDE_INVALID_DATA: return "invalid data"; + case EDE_SIG_E_B_V: return "signature expired before valid"; + case EDE_TOO_EARLY: return "too early"; + 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"; default: return "unknown"; } } diff --git a/src/dnsmasq/config.h b/src/dnsmasq/config.h index d73359be..144468a3 100644 --- a/src/dnsmasq/config.h +++ b/src/dnsmasq/config.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -15,14 +15,17 @@ */ #define FTABSIZ 150 /* max number of outstanding requests (default) */ -#define MAX_PROCS 60 /* max no children for TCP requests */ +#define MAX_PROCS 60 /* default max no children for TCP requests */ #define CHILD_LIFETIME 300 /* secs 'till terminated (RFC1035 suggests > 120s) */ #define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */ #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 SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */ #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ -#define DNSSEC_WORK 50 /* Max number of queries to validate one question */ +#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. */ +#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */ #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ #define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */ #define FORWARD_TEST 1000 /* try all servers every 1000 queries */ diff --git a/src/dnsmasq/conntrack.c b/src/dnsmasq/conntrack.c index 3260e4e4..5cc81680 100644 --- a/src/dnsmasq/conntrack.c +++ b/src/dnsmasq/conntrack.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 7e9298df..abc744a6 100644 --- a/src/dnsmasq/crypto.c +++ b/src/dnsmasq/crypto.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/dbus.c b/src/dnsmasq/dbus.c index ed5e44a9..ad6a4f37 100644 --- a/src/dnsmasq/dbus.c +++ b/src/dnsmasq/dbus.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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-common.c b/src/dnsmasq/dhcp-common.c index 10f1176b..48d6563d 100644 --- a/src/dnsmasq/dhcp-common.c +++ b/src/dnsmasq/dhcp-common.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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-protocol.h b/src/dnsmasq/dhcp-protocol.h index ad5d6140..3dde3543 100644 --- a/src/dnsmasq/dhcp-protocol.h +++ b/src/dnsmasq/dhcp-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 c7dd33be..b65facd8 100644 --- a/src/dnsmasq/dhcp.c +++ b/src/dnsmasq/dhcp.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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-protocol.h b/src/dnsmasq/dhcp6-protocol.h index e2b668a9..a23adac5 100644 --- a/src/dnsmasq/dhcp6-protocol.h +++ b/src/dnsmasq/dhcp6-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 a0266f7f..c9d54dcd 100644 --- a/src/dnsmasq/dhcp6.c +++ b/src/dnsmasq/dhcp6.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -92,7 +92,7 @@ void dhcp6_packet(time_t now) struct iface_param parm; struct cmsghdr *cmptr; struct msghdr msg; - int if_index = 0; + uint32_t if_index = 0; union { struct cmsghdr align; /* this ensures alignment */ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; diff --git a/src/dnsmasq/dns-protocol.h b/src/dnsmasq/dns-protocol.h index 76ac66a8..2777be93 100644 --- a/src/dnsmasq/dns-protocol.h +++ b/src/dnsmasq/dns-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -112,8 +112,11 @@ #define EDE_NO_AUTH 22 /* No Reachable Authority */ #define EDE_NETERR 23 /* Network error */ #define EDE_INVALID_DATA 24 /* Invalid Data */ - - +#define EDE_SIG_E_B_V 25 /* Signature Expired before Valid */ +#define EDE_TOO_EARLY 26 /* To Early */ +#define EDE_UNS_NS3_ITER 27 /* Unsupported NSEC3 Iterations Value */ +#define EDE_UNABLE_POLICY 28 /* Unable to conform to policy */ +#define EDE_SYNTHESIZED 29 /* Synthesized */ struct dns_header { diff --git a/src/dnsmasq/dnsmasq.c b/src/dnsmasq/dnsmasq.c index 2dd8102d..98ad525a 100644 --- a/src/dnsmasq/dnsmasq.c +++ b/src/dnsmasq/dnsmasq.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -36,12 +36,14 @@ static volatile int pipewrite; volatile char FTL_terminate = 0; static void set_dns_listeners(void); +static void set_tftp_listeners(void); static void check_dns_listeners(time_t now); static void sig_handler(int sig); static void async_event(int pipe, time_t now); static void fatal_event(struct event_desc *ev, char *msg); static int read_event(int fd, struct event_desc *evp, char **msg); static void poll_resolv(int force, int do_reload, time_t now); +static void tcp_init(void); int main_dnsmasq (int argc, char **argv) { @@ -135,19 +137,11 @@ int main_dnsmasq (int argc, char **argv) '.' or NAME_ESCAPE then all would have to be escaped, so the presentation format would be twice as long as the spec. */ daemon->keyname = safe_malloc((MAXDNAME * 2) + 1); - daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1); /* one char flag per possible RR in answer section (may get extended). */ daemon->rr_status_sz = 64; daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz); } #endif - -#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) - /* CONNTRACK UBUS code uses this buffer, so if not allocated above, - we need to allocate it here. */ - if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename) - daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1); -#endif #ifdef HAVE_DHCP if (!daemon->lease_file) @@ -378,6 +372,13 @@ int main_dnsmasq (int argc, char **argv) if (!enumerate_interfaces(1) || !enumerate_interfaces(0)) die(_("failed to find list of interfaces: %s"), NULL, EC_MISC); + +#ifdef HAVE_DHCP + /* Determine lease FQDNs after enumerate_interfaces() call, since it needs + to call get_domain and that's only valid for some domain configs once we + have interface addresses. */ + lease_calc_fqdns(); +#endif if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) { @@ -421,11 +422,13 @@ int main_dnsmasq (int argc, char **argv) daemon->numrrand = max_fd/3; /* safe_malloc returns zero'd memory */ daemon->randomsocks = safe_malloc(daemon->numrrand * sizeof(struct randfd)); + + tcp_init(); } #ifdef HAVE_INOTIFY - if ((daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6) - && (!option_bool(OPT_NO_RESOLV) || daemon->dynamic_dirs)) + if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) || + daemon->dynamic_dirs) inotify_dnsmasq_init(); else daemon->inotifyfd = -1; @@ -865,6 +868,8 @@ int main_dnsmasq (int argc, char **argv) if (option_bool(OPT_LOCAL_SERVICE)) my_syslog(LOG_INFO, _("DNS service limited to local subnets")); + else if (option_bool(OPT_LOCALHOST_SERVICE)) + my_syslog(LOG_INFO, _("DNS service limited to localhost")); } my_syslog(LOG_INFO, _("compile time options: %s"), compile_opts); @@ -1051,8 +1056,10 @@ int main_dnsmasq (int argc, char **argv) pid = getpid(); daemon->pipe_to_parent = -1; - for (i = 0; i < MAX_PROCS; i++) - daemon->tcp_pipes[i] = -1; + + if (daemon->port != 0) + for (i = 0; i < daemon->max_procs; i++) + daemon->tcp_pipes[i] = -1; #ifdef HAVE_INOTIFY /* Using inotify, have to select a resolv file at startup */ @@ -1079,7 +1086,12 @@ int main_dnsmasq (int argc, char **argv) (timeout == -1 || timeout > 1000)) timeout = 1000; - set_dns_listeners(); + if (daemon->port != 0) + set_dns_listeners(); + +#ifdef HAVE_TFTP + set_tftp_listeners(); +#endif #ifdef HAVE_DBUS if (option_bool(OPT_DBUS)) @@ -1264,8 +1276,9 @@ int main_dnsmasq (int argc, char **argv) check_ubus_listeners(); } #endif - - check_dns_listeners(now); + + if (daemon->port != 0) + check_dns_listeners(now); #ifdef HAVE_TFTP check_tftp_listeners(now); @@ -1538,10 +1551,15 @@ static void async_event(int pipe, time_t now) if (errno != EINTR) break; } - else - for (i = 0 ; i < MAX_PROCS; i++) + else if (daemon->port != 0) + for (i = 0 ; i < daemon->max_procs; i++) if (daemon->tcp_pids[i] == p) - daemon->tcp_pids[i] = 0; + { + daemon->tcp_pids[i] = 0; + /* tcp_pipes == -1 && tcp_pids == 0 required to free slot */ + if (daemon->tcp_pipes[i] == -1) + daemon->metrics[METRIC_TCP_CONNECTIONS]--; + } break; #if defined(HAVE_SCRIPT) @@ -1604,9 +1622,10 @@ static void async_event(int pipe, time_t now) case EVENT_TERM: /* Knock all our children on the head. */ - for (i = 0; i < MAX_PROCS; i++) - if (daemon->tcp_pids[i] != 0) - kill(daemon->tcp_pids[i], SIGALRM); + if (daemon->port != 0) + for (i = 0; i < daemon->max_procs; i++) + if (daemon->tcp_pids[i] != 0) + kill(daemon->tcp_pids[i], SIGALRM); #if defined(HAVE_SCRIPT) && defined(HAVE_DHCP) /* handle pending lease transitions */ @@ -1756,23 +1775,33 @@ void clear_cache_and_reload(time_t now) #endif } -static void set_dns_listeners(void) -{ - struct serverfd *serverfdp; - struct listener *listener; - struct randfd_list *rfl; - int i; - #ifdef HAVE_TFTP +static void set_tftp_listeners(void) +{ int tftp = 0; struct tftp_transfer *transfer; + struct listener *listener; + if (!option_bool(OPT_SINGLE_PORT)) for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) { tftp++; poll_listen(transfer->sockfd, POLLIN); } + + for (listener = daemon->listeners; listener; listener = listener->next) + /* tftp == 0 in single-port mode. */ + if (tftp <= daemon->tftp_max && listener->tftpfd != -1) + poll_listen(listener->tftpfd, POLLIN); +} #endif + +static void set_dns_listeners(void) +{ + struct serverfd *serverfdp; + struct listener *listener; + struct randfd_list *rfl; + int i; for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) poll_listen(serverfdp->fd, POLLIN); @@ -1786,7 +1815,7 @@ static void set_dns_listeners(void) poll_listen(rfl->rfd->fd, POLLIN); /* check to see if we have free tcp process slots. */ - for (i = MAX_PROCS - 1; i >= 0; i--) + for (i = daemon->max_procs - 1; i >= 0; i--) if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) break; @@ -1801,16 +1830,10 @@ static void set_dns_listeners(void) we'll be called again when a slot becomes available. */ if (listener->tcpfd != -1 && i >= 0) poll_listen(listener->tcpfd, POLLIN); - -#ifdef HAVE_TFTP - /* tftp == 0 in single-port mode. */ - if (tftp <= daemon->tftp_max && listener->tftpfd != -1) - poll_listen(listener->tftpfd, POLLIN); -#endif } if (!option_bool(OPT_DEBUG)) - for (i = 0; i < MAX_PROCS; i++) + for (i = 0; i < daemon->max_procs; i++) if (daemon->tcp_pipes[i] != -1) poll_listen(daemon->tcp_pipes[i], POLLIN); } @@ -1845,13 +1868,16 @@ static void check_dns_listeners(time_t now) to free the process slot. Once the child process has gone, poll() returns POLLHUP, not POLLIN, so have to check for both here. */ if (!option_bool(OPT_DEBUG)) - for (i = 0; i < MAX_PROCS; i++) + for (i = 0; i < daemon->max_procs; i++) if (daemon->tcp_pipes[i] != -1 && poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP) && !cache_recv_insert(now, daemon->tcp_pipes[i])) { close(daemon->tcp_pipes[i]); daemon->tcp_pipes[i] = -1; + /* tcp_pipes == -1 && tcp_pids == 0 required to free slot */ + if (daemon->tcp_pids[i] == 0) + daemon->metrics[METRIC_TCP_CONNECTIONS]--; } for (listener = daemon->listeners; listener; listener = listener->next) @@ -1860,17 +1886,12 @@ static void check_dns_listeners(time_t now) if (listener->fd != -1 && poll_check(listener->fd, POLLIN)) receive_query(listener, now); -#ifdef HAVE_TFTP - if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN)) - tftp_request(listener, now); -#endif - /* check to see if we have a free tcp process slot. Note that we can't assume that because we had at least one a poll() time, that we still do. There may be more waiting connections after poll() returns then free process slots. */ - for (i = MAX_PROCS - 1; i >= 0; i--) + for (i = daemon->max_procs - 1; i >= 0; i--) if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) break; @@ -1986,6 +2007,9 @@ static void check_dns_listeners(time_t now) /* i holds index of free slot */ daemon->tcp_pids[i] = p; daemon->tcp_pipes[i] = pipefd[0]; + daemon->metrics[METRIC_TCP_CONNECTIONS]++; + if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used) + daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS]; } close(confd); @@ -2171,7 +2195,11 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id) poll_reset(); if (fd != -1) poll_listen(fd, POLLIN); - set_dns_listeners(); + if (daemon->port != 0) + set_dns_listeners(); +#ifdef HAVE_TFTP + set_tftp_listeners(); +#endif set_log_writer(); #ifdef HAVE_DHCP6 @@ -2189,7 +2217,8 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id) now = dnsmasq_time(); check_log_writer(0); - check_dns_listeners(now); + if (daemon->port != 0) + check_dns_listeners(now); #ifdef HAVE_DHCP6 if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN)) @@ -2232,3 +2261,9 @@ void print_dnsmasq_version(const char *yellow, const char *green, const char *bo printf(_("Features: %s\n\n"), compile_opts); } /**************************************************************************************/ + +void tcp_init(void) +{ + daemon->tcp_pids = safe_malloc(daemon->max_procs*sizeof(pid_t)); + daemon->tcp_pipes = safe_malloc(daemon->max_procs*sizeof(int)); +} diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index 3f1d4751..a16c83b4 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -14,7 +14,7 @@ along with this program. If not, see . */ -#define COPYRIGHT "Copyright (c) 2000-2023 Simon Kelley" +#define COPYRIGHT "Copyright (c) 2000-2024 Simon Kelley" /* We do defines that influence behavior of stdio.h, so complain if included too early. */ @@ -281,7 +281,8 @@ struct event_desc { #define OPT_NORR 69 #define OPT_NO_IDENT 70 #define OPT_CACHE_RR 71 -#define OPT_LAST 72 +#define OPT_LOCALHOST_SERVICE 72 +#define OPT_LAST 73 #define OPTION_BITS (sizeof(unsigned int)*8) #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) @@ -340,7 +341,7 @@ union all_addr { in the cache flags. */ struct datablock { unsigned short rrtype; - unsigned char datalen; + unsigned char datalen; /* also length of SOA in negative records. */ char data[]; } rrdata; }; @@ -762,6 +763,9 @@ struct dyndir { #define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */ #define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */ #define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */ +#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */ +#define DNSSEC_FAIL_BADPACKET 0x0400 /* bad packet */ +#define DNSSEC_FAIL_WORK 0x0800 /* too much crypto */ #define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b)) @@ -799,7 +803,7 @@ struct frec { struct blockdata *stash; /* Saved reply, whilst we validate */ size_t stash_len; #ifdef HAVE_DNSSEC - int class, work_counter; + int class, work_counter, validate_counter; struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ struct frec *next_dependent; /* list of above. */ struct frec *blocking_query; /* Query which is blocking us. */ @@ -836,6 +840,12 @@ struct frec { #define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */ #define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */ +#define LIMIT_SIG_FAIL 0 +#define LIMIT_CRYPTO 1 +#define LIMIT_WORK 2 +#define LIMIT_NSEC3_ITERS 3 +#define LIMIT_MAX 4 + struct dhcp_lease { int clid_len; /* length of client identifier */ unsigned char *clid; /* clientid */ @@ -1238,16 +1248,14 @@ extern struct daemon { char *packet; /* packet buffer */ int packet_buff_sz; /* size of above */ char *namebuff; /* MAXDNAME size buffer */ -#if (defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)) || defined(HAVE_DNSSEC) - /* CONNTRACK UBUS code uses this buffer, as well as DNSSEC code. */ char *workspacename; -#endif #ifdef HAVE_DNSSEC char *keyname; /* MAXDNAME size buffer */ unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */ int rr_status_sz; int dnssec_no_time_check; int back_to_the_future; + int limit[LIMIT_MAX]; #endif struct frec *frec_list; struct frec_src *free_frec_src; @@ -1258,8 +1266,8 @@ extern struct daemon { struct server *srv_save; /* Used for resend on DoD */ size_t packet_len; /* " " */ int fd_save; /* " " */ - pid_t tcp_pids[MAX_PROCS]; - int tcp_pipes[MAX_PROCS]; + pid_t *tcp_pids; + int *tcp_pipes; int pipe_to_parent; int numrrand; struct randfd *randomsocks; @@ -1319,6 +1327,8 @@ extern struct daemon { /* file for packet dumps. */ int dumpfd; #endif + int max_procs; + uint max_procs_used; } *daemon; struct server_details { @@ -1383,6 +1393,7 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr); int is_rev_synth(int flag, union all_addr *addr, char *name); /* rfc1035.c */ +int do_doctor(struct dns_header *header, size_t qlen, char *namebuff); int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, char *name, int isExtract, int extrabytes); unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes); @@ -1393,7 +1404,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, void setup_reply(struct dns_header *header, unsigned int flags, int ede); int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, - int check_rebind, int no_cache_dnssec, int secure, int *doctored); + int check_rebind, int no_cache_dnssec, int secure); #if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) void report_addresses(struct dns_header *header, size_t len, u32 mark); #endif @@ -1424,10 +1435,12 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); /* dnssec.c */ #ifdef HAVE_DNSSEC size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz); -int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); -int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_count); +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_count); int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, - int check_unsigned, int *neganswer, int *nons, int *nsec_ttl); + int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count); int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); int setup_timestamp(void); @@ -1613,6 +1626,7 @@ void lease_update_from_configs(void); int do_script_run(time_t now); void rerun_scripts(void); void lease_find_interfaces(time_t now); +void lease_calc_fqdns(void); #ifdef HAVE_SCRIPT void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, unsigned int len, int delim); diff --git a/src/dnsmasq/dnssec.c b/src/dnsmasq/dnssec.c index 8a4f4fed..ed2f53ff 100644 --- a/src/dnsmasq/dnssec.c +++ b/src/dnsmasq/dnssec.c @@ -424,12 +424,24 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int return 1; } +int dec_counter(int *counter, char *message) +{ + if ((*counter)-- == 0) + { + my_syslog(LOG_WARNING, "limit exceeded: %s", message ? message : _("per-query crypto work")); + return 1; + } + + return 0; +} + /* Validate a single RRset (class, type, name) in the supplied DNS reply Return code: STAT_SECURE if it validates. STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. (In this case *wildcard_out points to the "body" of the wildcard within name.) STAT_BOGUS signature is wrong, bad packet. + STAT_ABANDONED validation abandoned do to excess resource usage. STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) STAT_NEED_DS need DS to complete validation (name is returned in keyname) @@ -444,10 +456,10 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int */ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, - int algo_in, int keytag_in, unsigned long *ttl_out) + int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter) { unsigned char *p; - int rdlen, j, name_labels, algo, labels, key_tag; + int rdlen, j, name_labels, algo, labels, key_tag, sig_fail_cnt; struct crec *crecp = NULL; short *rr_desc = rrfilter_desc(type); u32 sig_expiration, sig_inception; @@ -467,7 +479,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); /* Now try all the sigs to try and find one which validates */ - for (j = 0; j limit[LIMIT_SIG_FAIL], j = 0; j digest_size, algo)) - return STAT_SECURE; + if (algo_in == algo && keytag_in == key_tag) + { + if (dec_counter(validate_counter, NULL)) + return STAT_ABANDONED; + + if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) + return STAT_SECURE; + } } else { @@ -664,9 +681,22 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag && - crecp->uid == (unsigned int)class && - verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) - return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; + crecp->uid == (unsigned int)class) + { + if (dec_counter(validate_counter, NULL)) + return STAT_ABANDONED; + + if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) + return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; + + /* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing + keys, all of which we have to try. Since many failing keys is not likely for + a legitimate domain, set a limit on how many can fail. */ + if ((daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1)) > (int)daemon->metrics[METRIC_SIG_FAIL_HWM]) + daemon->metrics[METRIC_SIG_FAIL_HWM] = daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1); + if (dec_counter(&sig_fail_cnt, _("per-RRSet signature fails"))) + return STAT_ABANDONED; + } } } @@ -681,39 +711,45 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in STAT_OK Done, key(s) in cache. STAT_BOGUS No DNSKEYs found, which can be validated with DS, or self-sign for DNSKEY RRset is not valid, bad packet. + STAT_ABANDONED resource exhaustion. STAT_NEED_DS DS records to validate a key not found, name in keyname - STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname */ -int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_counter) { - unsigned char *psave, *p = (unsigned char *)(header+1); + unsigned char *psave, *p = (unsigned char *)(header+1), *keyaddr; struct crec *crecp, *recp1; - int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag; + int rc, j, qtype, qclass, rdlen, flags, algo, keytag, sigcnt, rrcnt; unsigned long ttl, sig_ttl; - struct blockdata *key; union all_addr a; - int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY; + int failflags = DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE; + char valid_digest[255]; + static unsigned char **cached_digest; + static size_t cached_digest_size = 0; - if (ntohs(header->qdcount) != 1 || - RCODE(header) == SERVFAIL || RCODE(header) == REFUSED || - !extract_name(header, plen, &p, name, 1, 4)) + if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4)) return STAT_BOGUS | DNSSEC_FAIL_NOKEY; GETSHORT(qtype, p); GETSHORT(qclass, p); - if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) + if (qtype != T_DNSKEY || qclass != class || + !explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) || + rrcnt == 0) return STAT_BOGUS | DNSSEC_FAIL_NOKEY; + if (sigcnt == 0) + return STAT_BOGUS | DNSSEC_FAIL_NOSIG; + /* See if we have cached a DS record which validates this key */ if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) { strcpy(keyname, name); return STAT_NEED_DS; } - + /* NOTE, we need to find ONE DNSKEY which matches the DS */ - for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) + for (j = ntohs(header->ancount); j != 0; j--) { /* Ensure we have type, class TTL and length */ if (!(rc = extract_name(header, plen, &p, name, 0, 10))) @@ -724,7 +760,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch GETLONG(ttl, p); GETSHORT(rdlen, p); - if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) + if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad packet */ if (qclass != class || qtype != T_DNSKEY || rc == 2) @@ -732,173 +768,204 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch p += rdlen; continue; } - + + if (rdlen < 5) + return STAT_BOGUS; /* min 1 byte key! */ + psave = p; GETSHORT(flags, p); if (*p++ != 3) - return STAT_BOGUS | DNSSEC_FAIL_NOKEY; + { + p = psave + rdlen; + continue; + } algo = *p++; - keytag = dnskey_keytag(algo, flags, p, rdlen - 4); - key = NULL; + keyaddr = p; + keytag = dnskey_keytag(algo, flags, keyaddr, rdlen - 4); - /* key must have zone key flag set */ - if (flags & 0x100) - { - key = blockdata_alloc((char*)p, rdlen - 4); - failflags &= ~DNSSEC_FAIL_NOZONE; - } - - p = psave; - - if (!ADD_RDLEN(header, p, plen, rdlen)) - { - if (key) - blockdata_free(key); - return STAT_BOGUS; /* bad packet */ - } + p = psave + rdlen; - /* No zone key flag or malloc failure */ - if (!key) + /* key must have zone key flag set */ + if (!(flags & 0x100)) continue; + failflags &= ~DNSSEC_FAIL_NOZONE; + + /* clear digest cache. */ + memset(valid_digest, 0, sizeof(valid_digest)); + for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) { void *ctx; unsigned char *digest, *ds_digest; const struct nettle_hash *hash; - int sigcnt, rrcnt; int wire_len; - if (recp1->addr.ds.algo == algo && - recp1->addr.ds.keytag == keytag && - recp1->uid == (unsigned int)class) - { - failflags &= ~DNSSEC_FAIL_NOKEY; + if ((recp1->flags & F_NEG) || + recp1->addr.ds.algo != algo || + recp1->addr.ds.keytag != keytag || + recp1->uid != (unsigned int)class) + continue; + + if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest)))) + continue; + + failflags &= ~DNSSEC_FAIL_NODSSUP; - if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest)))) - continue; - else - failflags &= ~DNSSEC_FAIL_NODSSUP; + if (recp1->addr.ds.keylen != (int)hash->digest_size || + !(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL))) + continue; + if (valid_digest[recp1->addr.ds.digest]) + digest = cached_digest[recp1->addr.ds.digest]; + else + { + /* computing a hash is a unit of crypto work. */ + if (dec_counter(validate_counter, NULL)) + return STAT_ABANDONED; + if (!hash_init(hash, &ctx, &digest)) continue; wire_len = to_wire(name); /* Note that digest may be different between DSs, so - we can't move this outside the loop. */ + we can't move this outside the loop. We keep + copies of each digest we make for this key, + so maximum digest work is O(keys x digests_types) + rather then O(keys x DSs) */ hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name); hash->update(ctx, (unsigned int)rdlen, psave); hash->digest(ctx, hash->digest_size, digest); from_wire(name); - - if (!(recp1->flags & F_NEG) && - recp1->addr.ds.keylen == (int)hash->digest_size && - (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) && - memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && - explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && - rrcnt != 0) - { - if (sigcnt == 0) - continue; - else - failflags &= ~DNSSEC_FAIL_NOSIG; - - rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, - NULL, key, rdlen - 4, algo, keytag, &sig_ttl); - failflags &= rc; - - if (STAT_ISEQUAL(rc, STAT_SECURE)) + if (recp1->addr.ds.digest >= cached_digest_size) + { + unsigned char **new; + + /* whine_malloc zeros memory */ + if ((new = whine_malloc((recp1->addr.ds.digest + 5) * sizeof(unsigned char *)))) { - valid = 1; - break; + if (cached_digest_size != 0) + { + memcpy(new, cached_digest, cached_digest_size * sizeof(unsigned char *)); + free(cached_digest); + } + + cached_digest_size = recp1->addr.ds.digest + 5; + cached_digest = new; + } + } + + if (recp1->addr.ds.digest < cached_digest_size) + { + if (!cached_digest[recp1->addr.ds.digest]) + cached_digest[recp1->addr.ds.digest] = whine_malloc(recp1->addr.ds.keylen); + + if (cached_digest[recp1->addr.ds.digest]) + { + memcpy(cached_digest[recp1->addr.ds.digest], digest, recp1->addr.ds.keylen); + valid_digest[recp1->addr.ds.digest] = 1; } } } - } - blockdata_free(key); - } - - if (valid) - { - /* DNSKEY RRset determined to be OK, now cache it. */ - cache_start_insert(); - - p = skip_questions(header, plen); - - for (j = ntohs(header->ancount); j != 0; j--) - { - /* Ensure we have type, class TTL and length */ - if (!(rc = extract_name(header, plen, &p, name, 0, 10))) - return STAT_BOGUS; /* bad packet */ - GETSHORT(qtype, p); - GETSHORT(qclass, p); - GETLONG(ttl, p); - GETSHORT(rdlen, p); - - /* TTL may be limited by sig. */ - if (sig_ttl < ttl) - ttl = sig_ttl; - - if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_BOGUS; /* bad packet */ - - if (qclass == class && rc == 1) + if (memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0) { - psave = p; + /* Found the key validated by a DS record. + Now check the self-sig for the entire key RRset using that key. + Note that validate_rrset() will never return STAT_NEED_KEY here, + since we supply the key it will use as an argument. */ + struct blockdata *key; + + if (!(key = blockdata_alloc((char *)keyaddr, rdlen - 4))) + break; + + rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, + NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter); - if (qtype == T_DNSKEY) + blockdata_free(key); + + if (STAT_ISEQUAL(rc, STAT_ABANDONED)) + return rc; + + /* can't validate KEY RRset with this key, see if there's another that + will, which is validated by another DS. */ + if (!STAT_ISEQUAL(rc, STAT_SECURE)) + break; + + /* DNSKEY RRset determined to be OK, now cache it. */ + cache_start_insert(); + + p = skip_questions(header, plen); + + for (j = ntohs(header->ancount); j != 0; j--) { - if (rdlen < 4) + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) return STAT_BOGUS; /* bad packet */ - GETSHORT(flags, p); - if (*p++ != 3) - return STAT_BOGUS; - algo = *p++; - keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); - if ((key = blockdata_alloc((char*)p, rdlen - 4))) - { - a.key.keylen = rdlen - 4; - a.key.keydata = key; - a.key.algo = algo; - a.key.keytag = keytag; - a.key.flags = flags; - - if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)) - { - blockdata_free(key); - return STAT_BOGUS; - } - else - { - a.log.keytag = keytag; - a.log.algo = algo; - if (algo_digest_name(algo)) - log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", 0); - else - log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0); - } - } + /* TTL may be limited by sig. */ + if (sig_ttl < ttl) + ttl = sig_ttl; + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ + + psave = p; + + if (qclass == class && rc == 1 && qtype == T_DNSKEY) + { + if (rdlen < 4) + return STAT_BOGUS; /* min 1 byte key! */ + + GETSHORT(flags, p); + if (*p++ == 3) + { + algo = *p++; + keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + + if (!(key = blockdata_alloc((char*)p, rdlen - 4))) + return STAT_BOGUS; + + a.key.keylen = rdlen - 4; + a.key.keydata = key; + a.key.algo = algo; + a.key.keytag = keytag; + a.key.flags = flags; + + if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)) + { + blockdata_free(key); + return STAT_BOGUS; + } + + a.log.keytag = keytag; + a.log.algo = algo; + if (algo_digest_name(algo)) + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", 0); + else + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0); + } + } + + p = psave + rdlen; } - - p = psave; + + /* commit cache insert. */ + cache_end_insert(); + return STAT_OK; } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_BOGUS; /* bad packet */ } - - /* commit cache insert. */ - cache_end_insert(); - return STAT_OK; } - + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0); return STAT_BOGUS | failflags; } @@ -916,9 +983,11 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname STAT_NEED_DS DS record needed. + STAT_ABANDONED resource exhaustion. */ -int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_counter) { unsigned char *p = (unsigned char *)(header+1); int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0; @@ -943,7 +1012,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char servfail = neganswer = nons = 1; else { - rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); + rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter); if (STAT_ISEQUAL(rc, STAT_INSECURE)) { @@ -977,6 +1046,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char for (i = 0; i < ntohs(header->ancount); i++) { + unsigned char *psave; + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) return STAT_BOGUS; /* bad packet */ @@ -987,15 +1058,16 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad packet */ + + psave = p; if (aclass == class && atype == T_DS && rc == 1) { int algo, digest, keytag; - unsigned char *psave = p; struct blockdata *key; - if (rdlen < 4) - return STAT_BOGUS; /* bad packet */ + if (rdlen < 5) + return STAT_BOGUS; /* min 1 byte digest! */ GETSHORT(keytag, p); algo = *p++; @@ -1006,7 +1078,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char a.log.keytag = keytag; a.log.algo = algo; a.log.digest = digest; - log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)", 0); + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)", 0); neg_ttl = ttl; } else if ((key = blockdata_alloc((char*)p, rdlen - 4))) @@ -1027,16 +1099,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char a.log.keytag = keytag; a.log.algo = algo; a.log.digest = digest; - log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu", 0); + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu", 0); found_supported = 1; } } - - p = psave; } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_BOGUS; /* bad packet */ + + p = psave + rdlen; } cache_end_insert(); @@ -1139,6 +1208,7 @@ static int hostname_cmp(const char *a, const char *b) } } +/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count, char *workspace1_in, char *workspace2, char *name, int type, int *nons) { @@ -1158,12 +1228,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi p = nsecs[i]; if (!extract_name(header, plen, &p, workspace1, 1, 10)) - return 0; + return DNSSEC_FAIL_BADPACKET; p += 8; /* class, type, TTL */ GETSHORT(rdlen, p); psave = p; - if (!extract_name(header, plen, &p, workspace2, 1, 10)) - return 0; + if (!extract_name(header, plen, &p, workspace2, 1, 0)) + return DNSSEC_FAIL_BADPACKET; /* If NSEC comes from wildcard expansion, use original wildcard as name for computation. */ @@ -1191,12 +1261,13 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi { /* 4035 para 5.4. Last sentence */ if (type == T_NSEC || type == T_RRSIG) - return 1; + return 0; /* NSEC with the same name as the RR we're testing, check that the type in question doesn't appear in the type map */ rdlen -= p - psave; - /* rdlen is now length of type map, and p points to it */ + /* rdlen is now length of type map, and p points to it + packet checked to be as long as rdlen implies in prove_non_existence() */ /* If we can prove that there's no NS record, return that information. */ if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0) @@ -1207,25 +1278,25 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi /* A CNAME answer would also be valid, so if there's a CNAME is should have been returned. */ if ((p[2] & (0x80 >> T_CNAME)) != 0) - return 0; + return DNSSEC_FAIL_NONSEC; /* If the SOA bit is set for a DS record, then we have the DS from the wrong side of the delegation. For the root DS, this is expected. */ if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) - return 0; + return DNSSEC_FAIL_NONSEC; } while (rdlen >= 2) { if (!CHECK_LEN(header, p, plen, rdlen)) - return 0; + return DNSSEC_FAIL_BADPACKET; if (p[0] == type >> 8) { /* Does the NSEC say our type exists? */ if (offset < p[1] && (p[offset+2] & mask) != 0) - return 0; + return DNSSEC_FAIL_NONSEC; break; /* finished checking */ } @@ -1234,24 +1305,24 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi p += p[1]; } - return 1; + return 0; } else if (rc == -1) { /* Normal case, name falls between NSEC name and next domain name, wrap around case, name falls between NSEC name (rc == -1) and end */ if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0) - return 1; + return 0; } else { /* wrap around case, name falls between start and next domain name */ if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 ) - return 1; + return 0; } } - return 0; + return DNSSEC_FAIL_NONSEC; } /* return digest length, or zero on error */ @@ -1325,23 +1396,23 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige for (i = 0; i < nsec_count; i++) if ((p = nsecs[i])) { - if (!extract_name(header, plen, &p, workspace1, 1, 0) || + if (!extract_name(header, plen, &p, workspace1, 1, 10) || !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) return 0; p += 8; /* class, type, TTL */ GETSHORT(rdlen, p); + psave = p; + + /* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */ p++; /* algo */ flags = *p++; /* flags */ p += 2; /* iterations */ salt_len = *p++; /* salt_len */ p += salt_len; /* salt */ hash_len = *p++; /* p now points to next hashed name */ - - if (!CHECK_LEN(header, p, plen, hash_len)) - return 0; - + if (digest_len == base32_len && hash_len == base32_len) { int rc = memcmp(workspace2, digest, digest_len); @@ -1349,7 +1420,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige if (rc == 0) { /* We found an NSEC3 whose hashed name exactly matches the query, so - we just need to check the type map. p points to the RR data for the record. */ + we just need to check the type map. p points to the RR data for the record. + Note we have packet length up to rdlen bytes checked. */ int offset = (type & 0xff) >> 3; int mask = 0x80 >> (type & 0x07); @@ -1357,15 +1429,12 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige p += hash_len; /* skip next-domain hash */ rdlen -= p - psave; - if (!CHECK_LEN(header, p, plen, rdlen)) - return 0; - if (rdlen >= 2 && p[0] == 0) { /* If we can prove that there's no NS record, return that information. */ if (nons && (p[2] & (0x80 >> T_NS)) != 0) *nons = 0; - + /* A CNAME answer would also be valid, so if there's a CNAME is should have been returned. */ if ((p[2] & (0x80 >> T_CNAME)) != 0) @@ -1424,8 +1493,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige return 0; } -static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, - char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons) +/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ +static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1, + char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter) { unsigned char *salt, *p, *digest; int digest_len, i, iterations, salt_len, base32_len, algo = 0; @@ -1445,9 +1515,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns for (i = 0; i < nsec_count; i++) { if (!(p = skip_name(nsecs[i], header, plen, 15))) - return 0; /* bad packet */ + return DNSSEC_FAIL_BADPACKET; /* bad packet */ - p += 10; /* type, class, TTL, rdlen */ + p += 10; /* type, class, TTL, rdlen */ algo = *p++; if ((hash = hash_find(nsec3_digest_name(algo)))) @@ -1456,23 +1526,18 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns /* No usable NSEC3s */ if (i == nsec_count) - return 0; + return DNSSEC_FAIL_NONSEC; p++; /* flags */ GETSHORT (iterations, p); - /* Upper-bound iterations, to avoid DoS. - Strictly, there are lower bounds for small keys, but - since we don't have key size info here, at least limit - to the largest bound, for 4096-bit keys. RFC 5155 10.3 */ - if (iterations > 2500) - return 0; + /* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */ + if (iterations > daemon->limit[LIMIT_NSEC3_ITERS]) + return DNSSEC_FAIL_NSEC3_ITERS; salt_len = *p++; salt = p; - if (!CHECK_LEN(header, salt, plen, salt_len)) - return 0; /* bad packet */ - + /* Now prune so we only have NSEC3 records with same iterations, salt and algo */ for (i = 0; i < nsec_count; i++) { @@ -1502,9 +1567,6 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (salt_len != *p++) continue; - if (!CHECK_LEN(header, p, plen, salt_len)) - return 0; /* bad packet */ - if (memcmp(p, salt, salt_len) != 0) continue; @@ -1512,11 +1574,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns nsecs[i] = nsec3p; } + if (dec_counter(validate_counter, NULL)) + return DNSSEC_FAIL_WORK; + if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) - return 0; + return DNSSEC_FAIL_NONSEC; if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name))) - return 1; + return 0; /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" or an answer inferred from a wildcard record. */ @@ -1531,15 +1596,20 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (wildname && hostname_isequal(closest_encloser, wildname)) break; + if (dec_counter(validate_counter, NULL)) + return DNSSEC_FAIL_WORK; + if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) - return 0; + return DNSSEC_FAIL_NONSEC; for (i = 0; i < nsec_count; i++) if ((p = nsecs[i])) { - if (!extract_name(header, plen, &p, workspace1, 1, 0) || - !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) - return 0; + if (!extract_name(header, plen, &p, workspace1, 1, 0)) + return DNSSEC_FAIL_BADPACKET; + + if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) + return DNSSEC_FAIL_NONSEC; if (digest_len == base32_len && memcmp(digest, workspace2, digest_len) == 0) @@ -1554,14 +1624,17 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns while ((closest_encloser = strchr(closest_encloser, '.'))); if (!closest_encloser || !next_closest) - return 0; + return DNSSEC_FAIL_NONSEC; /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ + if (dec_counter(validate_counter, NULL)) + return DNSSEC_FAIL_WORK; + if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) - return 0; + return DNSSEC_FAIL_NONSEC; if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) - return 0; + return DNSSEC_FAIL_NONSEC; /* Finally, check that there's no seat of wildcard synthesis */ if (!wildname) @@ -1572,17 +1645,22 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns wildcard--; *wildcard = '*'; + if (dec_counter(validate_counter, NULL)) + return DNSSEC_FAIL_WORK; + if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) - return 0; + return DNSSEC_FAIL_NONSEC; if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) - return 0; + return DNSSEC_FAIL_NONSEC; } - return 1; + return 0; } -static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl) +/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ +static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, + char *wildname, int *nons, int *nsec_ttl, int *validate_counter) { static unsigned char **nsecset = NULL, **rrsig_labels = NULL; static int nsecset_sz = 0, rrsig_labels_sz = 0; @@ -1594,7 +1672,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key /* Move to NS section */ if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) - return 0; + return DNSSEC_FAIL_BADPACKET; auth_start = p; @@ -1603,13 +1681,16 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key unsigned char *pstart = p; if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) - return 0; + return DNSSEC_FAIL_BADPACKET; GETSHORT(type, p); GETSHORT(class, p); GETLONG(ttl, p); GETSHORT(rdlen, p); - + + if (!CHECK_LEN(header, p, plen, rdlen)) + return DNSSEC_FAIL_BADPACKET; + if (class == qclass && (type == T_NSEC || type == T_NSEC3)) { if (nsec_ttl) @@ -1622,12 +1703,12 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key /* No mixed NSECing 'round here, thankyouverymuch */ if (type_found != 0 && type_found != type) - return 0; + return DNSSEC_FAIL_NONSEC; type_found = type; if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) - return 0; + return DNSSEC_FAIL_BADPACKET; if (type == T_NSEC) { @@ -1642,30 +1723,33 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key int res, j, rdlen1, type1, class1; if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) - return 0; + return DNSSEC_FAIL_BADPACKET; rrsig_labels[nsecs_found] = NULL; for (j = ntohs(header->nscount); j != 0; j--) { - if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) - return 0; + unsigned char *psav; + if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) + return DNSSEC_FAIL_BADPACKET; + GETSHORT(type1, p1); GETSHORT(class1, p1); p1 += 4; /* TTL */ GETSHORT(rdlen1, p1); + psav = p1; + if (!CHECK_LEN(header, p1, plen, rdlen1)) - return 0; + return DNSSEC_FAIL_BADPACKET; if (res == 1 && class1 == qclass && type1 == T_RRSIG) { int type_covered; - unsigned char *psav = p1; - + if (rdlen1 < 18) - return 0; /* bad packet */ + return DNSSEC_FAIL_BADPACKET; /* bad packet */ GETSHORT(type_covered, p1); @@ -1677,33 +1761,56 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key if (!rrsig_labels[nsecs_found]) rrsig_labels[nsecs_found] = p1; else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ - return 0; - } - p1 = psav; + return DNSSEC_FAIL_NONSEC; + } } - if (!ADD_RDLEN(header, p1, plen, rdlen1)) - return 0; + p1 = psav + rdlen1; } /* Must have found at least one sig. */ if (!rrsig_labels[nsecs_found]) - return 0; + return DNSSEC_FAIL_NONSEC; + } + else if (type == T_NSEC3) + { + /* Decode the packet structure enough to check that rdlen is big enough + to contain everything other than the type bitmap. + (packet checked to be long enough to contain rdlen above) + We don't need to do any further length checks in check_nes3_coverage() + or prove_non_existence_nsec3() */ + + int salt_len, hash_len; + unsigned char *psav = p; + + if (rdlen < 5) + return DNSSEC_FAIL_BADPACKET; + + p += 4; /* algo, flags, iterations */ + salt_len = *p++; /* salt_len */ + if (rdlen < (6 + salt_len)) + return DNSSEC_FAIL_BADPACKET; /* check up to hash_length */ + + p += salt_len; /* salt */ + hash_len = *p++; + if (rdlen < (6 + salt_len + hash_len)) + return DNSSEC_FAIL_BADPACKET; /* check to end of next hashed name */ + + p = psav; } nsecset[nsecs_found++] = pstart; } - if (!ADD_RDLEN(header, p, plen, rdlen)) - return 0; + p += rdlen; } if (type_found == T_NSEC) return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); else if (type_found == T_NSEC3) - return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons); + return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter); else - return 0; + return DNSSEC_FAIL_NONSEC; } /* Check signing status of name. @@ -1798,16 +1905,17 @@ static int zone_status(char *name, int class, char *keyname, time_t now) STAT_BOGUS signature is wrong, bad packet, no validation where there should be. STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class) STAT_NEED_DS need DS to complete validation (name is returned in keyname) + STAT_ABANDONED resource exhaustion. daemon->rr_status points to a char array which corressponds to the RRs in the answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't. When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section. Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode - is the nons argument is non-NULL. + if the nons argument is non-NULL. */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, - int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl) + int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter) { static unsigned char **targets = NULL; static int target_sz = 0; @@ -1816,7 +1924,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; int i, j, rc = STAT_INSECURE; int secure = STAT_SECURE; - + int rc_nsec; /* extend rr_status if necessary */ if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount)) { @@ -1982,9 +2090,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch { unsigned long sig_ttl; rc = validate_rrset(now, header, plen, class1, type1, sigcnt, - rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl); + rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter); - if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS)) + if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED)) { if (class) *class = class1; /* Class for DS or DNSKEY */ @@ -2018,8 +2126,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch That's not a problem since if the RRsets later fail we'll return BOGUS then. */ if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && - !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL)) - return STAT_BOGUS | DNSSEC_FAIL_NONSEC; + ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0) + return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec); rc = STAT_SECURE; } @@ -2044,20 +2152,24 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch /* For anything other than a DS record, this situation is OK if either the answer is in an unsigned zone, or there's a NSEC records. */ - if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl)) + if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0) { + if (rc_nsec & DNSSEC_FAIL_WORK) + return STAT_ABANDONED; + /* Empty DS without NSECS */ if (qtype == T_DS) - return STAT_BOGUS | DNSSEC_FAIL_NONSEC; + return STAT_BOGUS | rc_nsec; - if (!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE)) + if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) && + !STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE)) { if (class) *class = qclass; /* Class for NEED_DS or NEED_KEY */ return rc; } - return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */ + return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */ } } @@ -2139,6 +2251,8 @@ int errflags_to_ede(int status) return EDE_NO_DNSKEY; else if (status & DNSSEC_FAIL_NODSSUP) return EDE_USUPDS; + else if (status & DNSSEC_FAIL_NSEC3_ITERS) + return EDE_UNS_NS3_ITER; else if (status & DNSSEC_FAIL_NONSEC) return EDE_NO_NSEC; else if (status & DNSSEC_FAIL_INDET) diff --git a/src/dnsmasq/domain-match.c b/src/dnsmasq/domain-match.c index 2393f387..cf2da770 100644 --- a/src/dnsmasq/domain-match.c +++ b/src/dnsmasq/domain-match.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/domain.c b/src/dnsmasq/domain.c index ca3931de..f4c0bf71 100644 --- a/src/dnsmasq/domain.c +++ b/src/dnsmasq/domain.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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,12 +22,13 @@ static int match_domain(struct in_addr addr, struct cond_domain *c); static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c); static int match_domain6(struct in6_addr *addr, struct cond_domain *c); -int is_name_synthetic(int flags, char *name, union all_addr *addr) +int is_name_synthetic(int flags, char *name, union all_addr *addrp) { char *p; struct cond_domain *c = NULL; int prot = (flags & F_IPV6) ? AF_INET6 : AF_INET; - + union all_addr addr; + for (c = daemon->synth_domains; c; c = c->next) { int found = 0; @@ -74,7 +75,7 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr) if (!c->is6 && index <= ntohl(c->end.s_addr) - ntohl(c->start.s_addr)) { - addr->addr4.s_addr = htonl(ntohl(c->start.s_addr) + index); + addr.addr4.s_addr = htonl(ntohl(c->start.s_addr) + index); found = 1; } } @@ -86,8 +87,8 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr) index <= addr6part(&c->end6) - addr6part(&c->start6)) { u64 start = addr6part(&c->start6); - addr->addr6 = c->start6; - setaddr6part(&addr->addr6, start + index); + addr.addr6 = c->start6; + setaddr6part(&addr.addr6, start + index); found = 1; } } @@ -135,8 +136,8 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr) } } - if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, addr)) - found = (prot == AF_INET) ? match_domain(addr->addr4, c) : match_domain6(&addr->addr6, c); + if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, &addr)) + found = (prot == AF_INET) ? match_domain(addr.addr4, c) : match_domain6(&addr.addr6, c); } /* restore name */ @@ -148,7 +149,12 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr) if (found) - return 1; + { + if (addrp) + *addrp = addr; + + return 1; + } } return 0; diff --git a/src/dnsmasq/dump.c b/src/dnsmasq/dump.c index 793908a0..d1442f0f 100644 --- a/src/dnsmasq/dump.c +++ b/src/dnsmasq/dump.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/edns0.c b/src/dnsmasq/edns0.c index 800c51f4..598478fa 100644 --- a/src/dnsmasq/edns0.c +++ b/src/dnsmasq/edns0.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/forward.c b/src/dnsmasq/forward.c index 4a420b6d..2de082a2 100644 --- a/src/dnsmasq/forward.c +++ b/src/dnsmasq/forward.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -344,7 +344,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (ad_reqd) forward->flags |= FREC_AD_QUESTION; #ifdef HAVE_DNSSEC - forward->work_counter = DNSSEC_WORK; + forward->work_counter = daemon->limit[LIMIT_WORK]; + forward->validate_counter = daemon->limit[LIMIT_CRYPTO]; if (do_bit) forward->flags |= FREC_DO_QUESTION; #endif @@ -697,7 +698,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server { unsigned char *pheader, *sizep; struct ipsets *ipsets = NULL, *nftsets = NULL; - int munged = 0, is_sign; + int is_sign; unsigned int rcode = RCODE(header); size_t plen; /******** Pi-hole modification ********/ @@ -706,8 +707,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server (void)ad_reqd; (void)do_bit; - (void)bogusanswer; - + #ifdef HAVE_IPSET if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL)) ipsets = domain_find_sets(daemon->ipsets, daemon->namebuff); @@ -801,91 +801,108 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server server->flags |= SERV_WARNED_RECURSIVE; } - if (daemon->bogus_addr && rcode != NXDOMAIN && - check_for_bogus_wildcard(header, n, daemon->namebuff, now)) + if (header->hb3 & HB3_TC) { - munged = 1; - SET_RCODE(header, NXDOMAIN); - header->hb3 &= ~HB3_AA; - cache_secure = 0; - ede = EDE_BLOCKED; + log_query(F_UPSTREAM, NULL, NULL, "truncated", 0); + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); } - else - { - int doctored = 0; - - if (rcode == NXDOMAIN && - extract_request(header, n, daemon->namebuff, NULL)) - { - if (check_for_local_domain(daemon->namebuff, now) || - lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL)) - { - /* if we forwarded a query for a locally known name (because it was for - an unknown type) and the answer is NXDOMAIN, convert that to NODATA, - since we know that the domain exists, even if upstream doesn't */ - munged = 1; - header->hb3 |= HB3_AA; - SET_RCODE(header, NOERROR); - cache_secure = 0; - } - } - switch (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) + if (!(header->hb3 & HB3_TC) && (!bogusanswer || (header->hb4 & HB4_CD))) + { + if (rcode == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) && + (check_for_local_domain(daemon->namebuff, now) || lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))) { - case 1: - my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); - munged = 1; + /* if we forwarded a query for a locally known name (because it was for + an unknown type) and the answer is NXDOMAIN, convert that to NODATA, + since we know that the domain exists, even if upstream doesn't */ + header->hb3 |= HB3_AA; + SET_RCODE(header, NOERROR); + cache_secure = 0; + } + + if (daemon->doctors && do_doctor(header, n, daemon->namebuff)) + cache_secure = 0; + + /* check_for_bogus_wildcard() does it's own caching, so + don't call extract_addresses() if it triggers. */ + if (daemon->bogus_addr && rcode != NXDOMAIN && + check_for_bogus_wildcard(header, n, daemon->namebuff, now)) + { + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + SET_RCODE(header, NXDOMAIN); + header->hb3 &= ~HB3_AA; cache_secure = 0; ede = EDE_BLOCKED; - break; - - /* extract_addresses() found a malformed answer. */ - case 2: - munged = 1; - SET_RCODE(header, SERVFAIL); - cache_secure = 0; - ede = EDE_OTHER; - break; - - /* Pi-hole modification */ - case 99: - cache_secure = 0; - // Make a private copy of the pheader to ensure - // we are not accidentially rewriting what is in - // the pheader when we're creating a crafted reply - // further below (when a query is to be blocked) - if (pheader) - { - pheader_copy = calloc(1, plen); - memcpy(pheader_copy, pheader, plen); - } - - // Generate DNS packet for reply, a possibly existing pseudo header - // will be restored later inside resize_packet() - n = FTL_make_answer(header, ((char *) header) + 65536, n, &ede); - break; } + else + { + int rc = extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure); - if (rcode == NOERROR && rrfilter(header, &n, RRFILTER_CONF) > 0) - ede = EDE_FILTERED; + if (rc != 0) + { + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + cache_secure = 0; + } + + if (rc == 1) + { + my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); + ede = EDE_BLOCKED; + } + + if (rc == 2) + { + /* extract_addresses() found a malformed answer. */ + SET_RCODE(header, SERVFAIL); + ede = EDE_OTHER; + } + + /* Pi-hole modification */ + if(rc == 99) + { + cache_secure = 0; + // Make a private copy of the pheader to ensure + // we are not accidentially rewriting what is in + // the pheader when we're creating a crafted reply + // further below (when a query is to be blocked) + if (pheader) + { + pheader_copy = calloc(1, plen); + memcpy(pheader_copy, pheader, plen); + } + + // Generate DNS packet for reply, a possibly existing pseudo header + // will be restored later inside resize_packet() + n = FTL_make_answer(header, ((char *) header) + 65536, n, &ede); + } + } - if (doctored) - cache_secure = 0; + if (RCODE(header) == NOERROR && rrfilter(header, &n, RRFILTER_CONF) > 0) + ede = EDE_FILTERED; } #ifdef HAVE_DNSSEC - if (bogusanswer && !(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG)) - { - /* Bogus reply, turn into SERVFAIL */ - SET_RCODE(header, SERVFAIL); - munged = 1; - } - if (option_bool(OPT_DNSSEC_VALID)) { - header->hb4 &= ~HB4_AD; - - if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure) + if (bogusanswer) + { + if (!(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG)) + { + /* Bogus reply, turn into SERVFAIL */ + SET_RCODE(header, SERVFAIL); + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + ede = EDE_DNSSEC_BOGUS; + } + } + else if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure) header->hb4 |= HB4_AD; /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ @@ -893,20 +910,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server rrfilter(header, &n, RRFILTER_DNSSEC); } #endif - - /* do this after extract_addresses. Ensure NODATA reply and remove - nameserver info. */ - if (munged) - { - header->ancount = htons(0); - header->nscount = htons(0); - header->arcount = htons(0); - header->hb3 &= ~HB3_TC; - } - /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide - sections of the packet. Find the new length here and put back pseudoheader - if it was removed. */ + /* the code above can elide sections of the packet. Find the new length here + and put back pseudoheader if it was removed. */ n = resize_packet(header, n, pheader_copy ? pheader_copy : pheader, plen); /******** Pi-hole modification ********/ // The line above was modified to use @@ -931,6 +937,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server static void dnssec_validate(struct frec *forward, struct dns_header *header, ssize_t plen, int status, time_t now) { + struct frec *orig; + int log_resource = 0; + daemon->log_display_id = forward->frec_src.log_id; /* We've had a reply already, which we're validating. Ignore this duplicate */ @@ -955,6 +964,9 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, log_query(F_UPSTREAM | F_NOEXTRA, daemon->namebuff, NULL, "truncated", (forward->flags & FREC_DNSKEY_QUERY) ? T_DNSKEY : T_DS); } } + + /* Find the original query that started it all.... */ + for (orig = forward; orig->dependent; orig = orig->dependent); /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise would invite infinite loops, since the answers to DNSKEY and DS queries @@ -962,19 +974,17 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED)) { if (forward->flags & FREC_DNSKEY_QUERY) - status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); + status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter); else if (forward->flags & FREC_DS_QUERY) - status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); + status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter); else status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class, !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), - NULL, NULL, NULL); -#ifdef HAVE_DUMPFILE - if (STAT_ISEQUAL(status, STAT_BOGUS)) - dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS, - header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port); -#endif + NULL, NULL, NULL, &orig->validate_counter); } + + if (STAT_ISEQUAL(status, STAT_ABANDONED)) + log_resource = 1; /* Can't validate, as we're missing key data. Put this answer aside, whilst we get that. */ @@ -1018,18 +1028,19 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, return; } } + else if (orig->work_counter-- == 0) + { + my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries")); + log_resource = 1; + } else { struct server *server; - struct frec *orig; void *hash; size_t nn; int serverind, fd; struct randfd_list *rfds = NULL; - /* Find the original query that started it all.... */ - for (orig = forward; orig->dependent; orig = orig->dependent); - /* Make sure we don't expire and free the orig frec during the allocation of a new one: third arg of get_new_frec() does that. */ if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 && @@ -1038,7 +1049,6 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, daemon->keyname, forward->class, STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) && (hash = hash_questions(header, nn, daemon->namebuff)) && - --orig->work_counter != 0 && (fd = allocate_rfd(&rfds, server)) != -1 && (new = get_new_frec(now, server, 1))) { @@ -1104,6 +1114,21 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, status = STAT_ABANDONED; } + if (log_resource) + { + /* Log the actual validation that made us barf. */ + unsigned char *p = (unsigned char *)(header+1); + if (extract_name(header, plen, &p, daemon->namebuff, 0, 4) == 1) + my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."), + daemon->namebuff[0] ? daemon->namebuff : "."); + } + +#ifdef HAVE_DUMPFILE + if (STAT_ISEQUAL(status, STAT_BOGUS) || STAT_ISEQUAL(status, STAT_ABANDONED)) + dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS, + header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port); +#endif + /* Validated original answer, all done. */ if (!forward->dependent) return_reply(now, forward, header, plen, status); @@ -1112,7 +1137,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, /* validated subsidiary query/queries, (and cached result) pop that and return to the previous query/queries we were working on. */ struct frec *prev, *nxt = forward->dependent; - + free_frec(forward); while ((prev = nxt)) @@ -1378,6 +1403,12 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he log_query(F_SECSTAT, domain, &a, result, 0); } } + + if ((daemon->limit[LIMIT_CRYPTO] - forward->validate_counter) > (int)daemon->metrics[METRIC_CRYPTO_HWM]) + daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - forward->validate_counter; + + if ((daemon->limit[LIMIT_WORK] - forward->work_counter) > (int)daemon->metrics[METRIC_WORK_HWM]) + daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - forward->work_counter; #endif if (option_bool(OPT_NO_REBIND)) @@ -2107,7 +2138,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, /* Recurse down the key hierarchy */ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, int class, char *name, char *keyname, struct server *server, - int have_mark, unsigned int mark, int *keycount) + int have_mark, unsigned int mark, int *keycount, int *validatecount) { int first, last, start, new_status; unsigned char *packet = NULL; @@ -2121,20 +2152,34 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si int log_save; /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ - if (--(*keycount) == 0) - new_status = STAT_ABANDONED; - else if (STAT_ISEQUAL(status, STAT_NEED_KEY)) - new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); + if (STAT_ISEQUAL(status, STAT_NEED_KEY)) + new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class, validatecount); else if (STAT_ISEQUAL(status, STAT_NEED_DS)) - new_status = dnssec_validate_ds(now, header, n, name, keyname, class); + new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount); else new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC), - NULL, NULL, NULL); + NULL, NULL, NULL, validatecount); - if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY)) + if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY) && !STAT_ISEQUAL(new_status, STAT_ABANDONED)) break; - + + if ((*keycount)-- == 0) + { + my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries")); + new_status = STAT_ABANDONED; + } + + if (STAT_ISEQUAL(new_status, STAT_ABANDONED)) + { + /* Log the actual validation that made us barf. */ + unsigned char *p = (unsigned char *)(header+1); + if (extract_name(header, n, &p, daemon->namebuff, 0, 4) == 1) + my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."), + daemon->namebuff[0] ? daemon->namebuff : "."); + break; + } + /* Can't validate because we need a key/DS whose name now in keyname. Make query for same, and recurse to validate */ if (!packet) @@ -2148,7 +2193,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si new_status = STAT_ABANDONED; break; } - + m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz); @@ -2163,10 +2208,11 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si daemon->log_display_id = ++daemon->log_id; log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr, - STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0); - - new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); - + STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0); + + new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, + have_mark, mark, keycount, validatecount); + daemon->log_display_id = log_save; if (!STAT_ISEQUAL(new_status, STAT_OK)) @@ -2508,9 +2554,10 @@ unsigned char *tcp_request(int confd, time_t now, #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC)) { - int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ + int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ + int validatecount = daemon->limit[LIMIT_CRYPTO]; int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, - serv, have_mark, mark, &keycount); + serv, have_mark, mark, &keycount, &validatecount); char *result, *domain = "result"; union all_addr a; @@ -2536,6 +2583,12 @@ unsigned char *tcp_request(int confd, time_t now, } log_query(F_SECSTAT, domain, &a, result, 0); + + if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM]) + daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - validatecount; + + if ((daemon->limit[LIMIT_WORK] - keycount) > (int)daemon->metrics[METRIC_WORK_HWM]) + daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - keycount; } #endif diff --git a/src/dnsmasq/hash-questions.c b/src/dnsmasq/hash-questions.c index c1ee1354..e6304ac8 100644 --- a/src/dnsmasq/hash-questions.c +++ b/src/dnsmasq/hash-questions.c @@ -165,7 +165,7 @@ static void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; for (i = 0, j = 0; i < 16; ++i, j += 4) - m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); + m[i] = (((WORD)data[j]) << 24) | (((WORD)data[j + 1]) << 16) | (((WORD)data[j + 2]) << 8) | (((WORD)data[j + 3])); for ( ; i < 64; ++i) m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; diff --git a/src/dnsmasq/helper.c b/src/dnsmasq/helper.c index 2d0ca303..a59a0a78 100644 --- a/src/dnsmasq/helper.c +++ b/src/dnsmasq/helper.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/inotify.c b/src/dnsmasq/inotify.c index d0efd892..0c775de8 100644 --- a/src/dnsmasq/inotify.c +++ b/src/dnsmasq/inotify.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -94,7 +94,7 @@ void inotify_dnsmasq_init() if (daemon->inotifyfd == -1) die(_("failed to create inotify: %s"), NULL, EC_MISC); - if (option_bool(OPT_NO_RESOLV)) + if (daemon->port == 0 || option_bool(OPT_NO_RESOLV)) return; for (res = daemon->resolv_files; res; res = res->next) diff --git a/src/dnsmasq/ip6addr.h b/src/dnsmasq/ip6addr.h index a66e4aed..39dc8e2a 100644 --- a/src/dnsmasq/ip6addr.h +++ b/src/dnsmasq/ip6addr.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/lease.c b/src/dnsmasq/lease.c index 384efa9b..55e8443b 100644 --- a/src/dnsmasq/lease.c +++ b/src/dnsmasq/lease.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -15,7 +15,6 @@ */ #include "dnsmasq.h" - #ifdef HAVE_DHCP static struct dhcp_lease *leases = NULL, *old_leases = NULL; @@ -28,8 +27,7 @@ static int read_leases(time_t now, FILE *leasestream) struct dhcp_lease *lease; int clid_len, hw_len, hw_type; int items; - char *domain = NULL; - + *daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0'; /* client-id max length is 255 which is 255*2 digits + 254 colons @@ -69,8 +67,8 @@ static int read_leases(time_t now, FILE *leasestream) if (inet_pton(AF_INET, daemon->namebuff, &addr.addr4)) { - if ((lease = lease4_allocate(addr.addr4))) - domain = get_domain(lease->addr); + lease = lease4_allocate(addr.addr4); + hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type); /* For backwards compatibility, no explicit MAC address type means ether. */ @@ -90,10 +88,7 @@ static int read_leases(time_t now, FILE *leasestream) } if ((lease = lease6_allocate(&addr.addr6, lease_type))) - { - lease_set_iaid(lease, strtoul(s, NULL, 10)); - domain = get_domain6(&lease->addr6); - } + lease_set_iaid(lease, strtoul(s, NULL, 10)); } #endif else @@ -114,7 +109,7 @@ static int read_leases(time_t now, FILE *leasestream) hw_len, hw_type, clid_len, now, 0); if (strcmp(daemon->dhcp_buff, "*") != 0) - lease_set_hostname(lease, daemon->dhcp_buff, 0, domain, NULL); + lease_set_hostname(lease, daemon->dhcp_buff, 0, NULL, NULL); ei = atol(daemon->dhcp_buff3); @@ -946,6 +941,36 @@ static void kill_name(struct dhcp_lease *lease) lease->hostname = lease->fqdn = NULL; } +void lease_calc_fqdns(void) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + { + char *domain; + + if (lease->hostname) + { +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + domain = get_domain6(&lease->addr6); + else +#endif + domain = get_domain(lease->addr); + + if (domain) + { + /* This is called only during startup, before forking, hence safe_malloc() */ + lease->fqdn = safe_malloc(strlen(lease->hostname) + strlen(domain) + 2); + + strcpy(lease->fqdn, lease->hostname); + strcat(lease->fqdn, "."); + strcat(lease->fqdn, domain); + } + } + } +} + void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain) { struct dhcp_lease *lease_tmp; diff --git a/src/dnsmasq/log.c b/src/dnsmasq/log.c index 6cd8a696..eb0f2cef 100644 --- a/src/dnsmasq/log.c +++ b/src/dnsmasq/log.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/loop.c b/src/dnsmasq/loop.c index 927d2ede..f87293f8 100644 --- a/src/dnsmasq/loop.c +++ b/src/dnsmasq/loop.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/metrics.c b/src/dnsmasq/metrics.c index 050ccb35..e59e7627 100644 --- a/src/dnsmasq/metrics.c +++ b/src/dnsmasq/metrics.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -24,6 +24,9 @@ const char * metric_names[] = { "dns_local_answered", "dns_stale_answered", "dns_unanswered", + "dnssec_max_crypto_use", + "dnssec_max_sig_fail", + "dnssec_max_work", "bootp", "pxe", "dhcp_ack", @@ -39,6 +42,7 @@ const char * metric_names[] = { "leases_pruned_4", "leases_allocated_6", "leases_pruned_6", + "tcp_connections", }; const char* get_metric_name(int i) { diff --git a/src/dnsmasq/metrics.h b/src/dnsmasq/metrics.h index 22e9e486..67cb3bfe 100644 --- a/src/dnsmasq/metrics.h +++ b/src/dnsmasq/metrics.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -23,6 +23,9 @@ enum { METRIC_DNS_LOCAL_ANSWERED, METRIC_DNS_STALE_ANSWERED, METRIC_DNS_UNANSWERED_QUERY, + METRIC_CRYPTO_HWM, + METRIC_SIG_FAIL_HWM, + METRIC_WORK_HWM, METRIC_BOOTP, METRIC_PXE, METRIC_DHCPACK, @@ -38,6 +41,7 @@ enum { METRIC_LEASES_PRUNED_4, METRIC_LEASES_ALLOCATED_6, METRIC_LEASES_PRUNED_6, + METRIC_TCP_CONNECTIONS, __METRIC_MAX, }; diff --git a/src/dnsmasq/netlink.c b/src/dnsmasq/netlink.c index 8d431fec..ef4b5fec 100644 --- a/src/dnsmasq/netlink.c +++ b/src/dnsmasq/netlink.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/network.c b/src/dnsmasq/network.c index ed1ffd49..60799d48 100644 --- a/src/dnsmasq/network.c +++ b/src/dnsmasq/network.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -929,15 +929,24 @@ static int make_sock(union mysockaddr *addr, int type, int dienow) errno = errsave; - if (dienow) + /* Failure to bind addresses given by --listen-address at this point + because there's no interface with the address is OK if we're doing bind-dynamic. + If/when an interface is created with the relevant address we'll notice + and attempt to bind it then. This is in the generic error path so we close the socket, + but EADDRNOTAVAIL is only a possible error from bind() + + When a new address is created and we call this code again (dienow == 0) there + may still be configured addresses when don't exist, (consider >1 --listen-address, + when the first is created, the second will still be missing) so we suppress + EADDRNOTAVAIL even in that case to avoid confusing log entries. + */ + if (!option_bool(OPT_CLEVERBIND) || errno != EADDRNOTAVAIL) { - /* failure to bind addresses given by --listen-address at this point - is OK if we're doing bind-dynamic */ - if (!option_bool(OPT_CLEVERBIND)) + if (dienow) die(s, daemon->addrbuff, EC_BADNET); + else + my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno)); } - else - my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno)); return -1; } diff --git a/src/dnsmasq/nftset.c b/src/dnsmasq/nftset.c index 82b63393..123326ca 100644 --- a/src/dnsmasq/nftset.c +++ b/src/dnsmasq/nftset.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -43,7 +43,8 @@ int add_to_nftset(const char *setname, const union all_addr *ipaddr, int flags, const char *cmd = remove ? cmd_del : cmd_add; int ret, af = (flags & F_IPV4) ? AF_INET : AF_INET6; size_t new_sz; - char *new, *err, *nl; + char *err_str, *new, *nl; + const char *err; static char *cmd_buf = NULL; static size_t cmd_buf_sz = 0; @@ -78,14 +79,19 @@ int add_to_nftset(const char *setname, const union all_addr *ipaddr, int flags, } ret = nft_run_cmd_from_buffer(ctx, cmd_buf); - err = (char *)nft_ctx_get_error_buffer(ctx); + err = nft_ctx_get_error_buffer(ctx); if (ret != 0) { /* Log only first line of error return. */ - if ((nl = strchr(err, '\n'))) - *nl = 0; - my_syslog(LOG_ERR, "nftset %s %s", setname, err); + if ((err_str = whine_malloc(strlen(err) + 1))) + { + strcpy(err_str, err); + if ((nl = strchr(err_str, '\n'))) + *nl = 0; + my_syslog(LOG_ERR, "nftset %s %s", setname, err_str); + free(err_str); + } } return ret; diff --git a/src/dnsmasq/option.c b/src/dnsmasq/option.c index 647525af..249a6f35 100644 --- a/src/dnsmasq/option.c +++ b/src/dnsmasq/option.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -194,6 +194,8 @@ struct myoption { #define LOPT_FILTER_RR 381 #define LOPT_NO_DHCP6 382 #define LOPT_NO_DHCP4 383 +#define LOPT_MAX_PROCS 384 +#define LOPT_DNSSEC_LIMITS 385 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -225,7 +227,7 @@ static const struct myoption opts[] = { "domain-suffix", 1, 0, 's' }, { "interface", 1, 0, 'i' }, { "listen-address", 1, 0, 'a' }, - { "local-service", 0, 0, LOPT_LOCAL_SERVICE }, + { "local-service", 2, 0, LOPT_LOCAL_SERVICE }, { "bogus-priv", 0, 0, 'b' }, { "bogus-nxdomain", 1, 0, 'B' }, { "ignore-address", 1, 0, LOPT_IGNORE_ADDR }, @@ -367,6 +369,7 @@ static const struct myoption opts[] = { "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK }, { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME }, { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP }, + { "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS }, { "dhcp-relay", 1, 0, LOPT_RELAY }, { "ra-param", 1, 0, LOPT_RA_PARAM }, { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP }, @@ -388,6 +391,7 @@ static const struct myoption opts[] = { "fast-dns-retry", 2, 0, LOPT_FAST_RETRY }, { "use-stale-cache", 2, 0 , LOPT_STALE_CACHE }, { "no-ident", 0, 0, LOPT_NO_IDENT }, + { "max-tcp-connections", 1, 0, LOPT_MAX_PROCS }, { NULL, 0, 0, 0 } }; @@ -570,12 +574,13 @@ static struct { { LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL }, { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL }, { LOPT_DNSSEC_STAMP, ARG_ONE, "", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL }, + { LOPT_DNSSEC_LIMITS, ARG_ONE, ",..", gettext_noop("Set resource limits for DNSSEC validation"), NULL }, { LOPT_RA_PARAM, ARG_DUP, ",[mtu:||off,][,][,]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL }, { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL }, { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL }, { LOPT_LOG_DEBUG, OPT_LOG_DEBUG, NULL, gettext_noop("Log debugging information."), NULL }, - { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL }, + { LOPT_LOCAL_SERVICE, ARG_ONE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL }, { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops."), NULL }, { LOPT_IGNORE_ADDR, ARG_DUP, "", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, { LOPT_DHCPTTL, ARG_ONE, "", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL }, @@ -589,6 +594,7 @@ static struct { { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL }, { LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL }, { LOPT_CACHE_RR, ARG_DUP, "", gettext_noop("Cache this DNS resource record type."), NULL }, + { LOPT_MAX_PROCS, ARG_ONE, "", gettext_noop("Maximum number of concurrent tcp connections."), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -1283,6 +1289,17 @@ static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, in return NULL; } +static void if_names_add(const char *ifname) +{ + struct iname *new = opt_malloc(sizeof(struct iname)); + new->next = daemon->if_names; + daemon->if_names = new; + /* new->name may be NULL if someone does + "interface=" to disable all interfaces except loop. */ + new->name = opt_string_alloc(ifname); + new->flags = 0; +} + #ifdef HAVE_DHCP static int is_tag_prefix(char *arg) @@ -1412,7 +1429,6 @@ static void dhcp_opt_free(struct dhcp_opt *opt) free(opt); } - /* This is too insanely large to keep in-line in the switch */ static int parse_dhcp_opt(char *errstr, char *arg, int flags) { @@ -2836,14 +2852,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case 'i': /* --interface */ do { - struct iname *new = opt_malloc(sizeof(struct iname)); - comma = split(arg); - new->next = daemon->if_names; - daemon->if_names = new; - /* new->name may be NULL if someone does - "interface=" to disable all interfaces except loop. */ - new->name = opt_string_alloc(arg); - new->flags = 0; + comma = split(arg); + if_names_add(arg); arg = comma; } while (arg); break; @@ -3409,6 +3419,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err(gen_err); else if (daemon->max_logs > 100) daemon->max_logs = 100; + break; + + case LOPT_LOCAL_SERVICE: /* --local-service */ + if (!arg || !strcmp(arg, "net")) + set_option_bool(OPT_LOCAL_SERVICE); + else if (!strcmp(arg, "host")) + set_option_bool(OPT_LOCALHOST_SERVICE); + else + ret_err(gen_err); break; case 'P': /* --edns-packet-max */ @@ -5246,6 +5265,24 @@ err: } #ifdef HAVE_DNSSEC + case LOPT_DNSSEC_LIMITS: + { + int lim, val; + + for (lim = LIMIT_SIG_FAIL; arg && lim < LIMIT_MAX ; lim++, arg = comma) + { + comma = split(arg); + + if (!atoi_check(arg, &val)) + ret_err(gen_err); + + if (val != 0) + daemon->limit[lim] = val; + } + + break; + } + case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */ daemon->timestamp_file = opt_string_alloc(arg); break; @@ -5317,7 +5354,17 @@ err: break; } #endif - + + case LOPT_MAX_PROCS: /* --max-tcp-connections */ + { + int max_procs; + /* Don't accept numbers less than 1. */ + if (!atoi_check(arg, &max_procs) || max_procs < 1) + ret_err(gen_err); + daemon->max_procs = max_procs; + break; + } + default: ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)")); @@ -5822,6 +5869,7 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon = opt_malloc(sizeof(struct daemon)); memset(daemon, 0, sizeof(struct daemon)); daemon->namebuff = buff; + daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1); daemon->addrbuff = safe_malloc(ADDRSTRLEN); /* Set defaults - everything else is zero or NULL */ @@ -5845,6 +5893,13 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon->soa_expiry = SOA_EXPIRY; daemon->randport_limit = 1; daemon->host_index = SRC_AH; + daemon->max_procs = MAX_PROCS; +#ifdef HAVE_DNSSEC + daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL; + daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO; + daemon->limit[LIMIT_WORK] = DNSSEC_LIMIT_WORK; + daemon->limit[LIMIT_NSEC3_ITERS] = DNSSEC_LIMIT_NSEC3_ITERS; +#endif /* See comment above make_servers(). Optimises server-read code. */ mark_servers(0); @@ -6148,7 +6203,16 @@ void read_opts(int argc, char **argv, char *compile_opts) /* If there's access-control config, then ignore --local-service, it's intended as a system default to keep otherwise unconfigured installations safe. */ if (daemon->if_names || daemon->if_except || daemon->if_addrs || daemon->authserver) - reset_option_bool(OPT_LOCAL_SERVICE); + { + reset_option_bool(OPT_LOCAL_SERVICE); + reset_option_bool(OPT_LOCALHOST_SERVICE); + } + else if (option_bool(OPT_LOCALHOST_SERVICE) && !option_bool(OPT_LOCAL_SERVICE)) + { + /* listen only on localhost, emulate --interface=lo --bind-interfaces */ + if_names_add(NULL); + set_option_bool(OPT_NOWILD); + } if (testmode) { diff --git a/src/dnsmasq/outpacket.c b/src/dnsmasq/outpacket.c index 3c27ee4b..1a29f613 100644 --- a/src/dnsmasq/outpacket.c +++ b/src/dnsmasq/outpacket.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/pattern.c b/src/dnsmasq/pattern.c index 384d29df..cf19eb0f 100644 --- a/src/dnsmasq/pattern.c +++ b/src/dnsmasq/pattern.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/poll.c b/src/dnsmasq/poll.c index 89ba6071..24568cc4 100644 --- a/src/dnsmasq/poll.c +++ b/src/dnsmasq/poll.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/radv-protocol.h b/src/dnsmasq/radv-protocol.h index a097427b..1e77f2c0 100644 --- a/src/dnsmasq/radv-protocol.h +++ b/src/dnsmasq/radv-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/radv.c b/src/dnsmasq/radv.c index a4af3445..d2d33905 100644 --- a/src/dnsmasq/radv.c +++ b/src/dnsmasq/radv.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/rfc1035.c b/src/dnsmasq/rfc1035.c index 05c8090f..06d3067c 100644 --- a/src/dnsmasq/rfc1035.c +++ b/src/dnsmasq/rfc1035.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -385,14 +385,23 @@ static int private_net6(struct in6_addr *a, int ban_localhost) ((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */ } -static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, int *doctored) +int do_doctor(struct dns_header *header, size_t qlen, char *namebuff) { + unsigned char *p; int i, qtype, qclass, rdlen; - - for (i = count; i != 0; i--) + int done = 0; + + if (!(p = skip_questions(header, qlen))) + return done; + + for (i = 0; i < ntohs(header->ancount) + ntohs(header->arcount); i++) { - if (!(p = skip_name(p, header, qlen, 10))) - return 0; /* bad packet */ + /* Skip over auth section */ + if (i == ntohs(header->ancount) && !(p = skip_section(p, ntohs(header->nscount), header, qlen))) + return done; + + if (!extract_name(header, qlen, &p, namebuff, 1, 10)) + return done; /* bad packet */ GETSHORT(qtype, p); GETSHORT(qclass, p); @@ -402,98 +411,190 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * if (qclass == C_IN && qtype == T_A) { struct doctor *doctor; - struct in_addr addr; + union all_addr addr; if (!CHECK_LEN(header, p, qlen, INADDRSZ)) - return 0; + return done; /* alignment */ - memcpy(&addr, p, INADDRSZ); + memcpy(&addr.addr4, p, INADDRSZ); for (doctor = daemon->doctors; doctor; doctor = doctor->next) { if (doctor->end.s_addr == 0) { - if (!is_same_net(doctor->in, addr, doctor->mask)) + if (!is_same_net(doctor->in, addr.addr4, doctor->mask)) continue; } - else if (ntohl(doctor->in.s_addr) > ntohl(addr.s_addr) || - ntohl(doctor->end.s_addr) < ntohl(addr.s_addr)) + else if (ntohl(doctor->in.s_addr) > ntohl(addr.addr4.s_addr) || + ntohl(doctor->end.s_addr) < ntohl(addr.addr4.s_addr)) continue; - addr.s_addr &= ~doctor->mask.s_addr; - addr.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); + addr.addr4.s_addr &= ~doctor->mask.s_addr; + addr.addr4.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); /* Since we munged the data, the server it came from is no longer authoritative */ header->hb3 &= ~HB3_AA; - *doctored = 1; - memcpy(p, &addr, INADDRSZ); +#ifdef HAVE_DNSSEC + /* remove validated flag from this RR, since we changed it! */ + if (option_bool(OPT_DNSSEC_VALID) && i < ntohs(header->ancount)) + daemon->rr_status[i] = 0; +#endif + done = 1; + memcpy(p, &addr.addr4, INADDRSZ); + log_query(F_FORWARD | F_CONFIG | F_IPV4, namebuff, &addr, NULL, 0); break; } } if (!ADD_RDLEN(header, p, qlen, rdlen)) - return 0; /* bad packet */ + return done; /* bad packet */ } - - return p; + + return done; } -static int find_soa(struct dns_header *header, size_t qlen, int *doctored) +/* Find SOA RR in auth section to get TTL for negative caching of name. + Cache said SOA and return the difference in length between name and the name of the + SOA RR so we can look it up again. +*/ +static int find_soa(struct dns_header *header, size_t qlen, char *name, int *substring, unsigned long *ttlp, int no_cache, time_t now) { - unsigned char *p; + unsigned char *p, *psave; int qtype, qclass, rdlen; - unsigned long ttl, minttl = ULONG_MAX; - int i, found_soa = 0; - - /* first move to NS section and find TTL from any SOA section */ + unsigned long ttl, minttl; + int i, j; + size_t name_len, soa_len, len; + union all_addr addr; + + /* first move to NS section and find TTL from SOA RR */ if (!(p = skip_questions(header, qlen)) || - !(p = do_doctor(p, ntohs(header->ancount), header, qlen, doctored))) + !(p = skip_section(p, ntohs(header->ancount), header, qlen))) return 0; /* bad packet */ + + name_len = strlen(name); - for (i = ntohs(header->nscount); i != 0; i--) + if (substring) + *substring = name_len; + + if (ttlp) + *ttlp = daemon->neg_ttl; + + for (i = 0; i < ntohs(header->nscount); i++) { - if (!(p = skip_name(p, header, qlen, 10))) + if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0)) return 0; /* bad packet */ GETSHORT(qtype, p); GETSHORT(qclass, p); GETLONG(ttl, p); GETSHORT(rdlen, p); + + psave = p; if ((qclass == C_IN) && (qtype == T_SOA)) { - found_soa = 1; - if (ttl < minttl) - minttl = ttl; + soa_len = strlen(daemon->workspacename); - /* MNAME */ - if (!(p = skip_name(p, header, qlen, 0))) - return 0; - /* RNAME */ - if (!(p = skip_name(p, header, qlen, 20))) - return 0; - p += 16; /* SERIAL REFRESH RETRY EXPIRE */ - - GETLONG(ttl, p); /* minTTL */ - if (ttl < minttl) - minttl = ttl; + /* SOA must be for the name we're interested in. */ + if (soa_len <= name_len && memcmp(daemon->workspacename, name + name_len - soa_len, soa_len) == 0) + { + int prefix = name_len - soa_len; + + if (!no_cache) + { + if (!(addr.rrblock.rrdata = blockdata_alloc(NULL, 0))) + return 0; + addr.rrblock.rrtype = T_SOA; + addr.rrblock.datalen = 0; + } + + for (j = 0; j < 2; j++) /* MNAME, RNAME */ + { + if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0)) + { + if (!no_cache) + blockdata_free(addr.rrblock.rrdata); + return 0; + } + + if (!no_cache) + { + len = to_wire(daemon->workspacename); + if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, daemon->workspacename, len)) + { + blockdata_free(addr.rrblock.rrdata); + return 0; + } + + addr.rrblock.datalen += len; + } + } + + if (!CHECK_LEN(header, p, qlen, 20)) + { + if (!no_cache) + blockdata_free(addr.rrblock.rrdata); + return 0; + } + + /* rest of RR */ + if (!no_cache && !blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20)) + { + blockdata_free(addr.rrblock.rrdata); + return 0; + } + + addr.rrblock.datalen += 20; + + if (!no_cache) + { + int secflag = 0; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0) + { + secflag = F_DNSSECOK; + + /* limit TTL based on signature. */ + if (daemon->rr_status[i + ntohs(header->ancount)] < ttl) + ttl = daemon->rr_status[i + ntohs(header->ancount)]; + } +#endif + + if (!cache_insert(name + prefix, &addr, C_IN, now, ttl, F_FORWARD | F_RR | F_KEYTAG | secflag)) + { + blockdata_free(addr.rrblock.rrdata); + return 0; + } + } + + p += 16; /* SERIAL REFRESH RETRY EXPIRE */ + + GETLONG(minttl, p); /* minTTL */ + if (ttl < minttl) + minttl = ttl; + + if (substring) + *substring = prefix; + + if (ttlp) + *ttlp = minttl; + + return 1; + } } - else if (!ADD_RDLEN(header, p, qlen, rdlen)) + + p = psave; + + if (!ADD_RDLEN(header, p, qlen, rdlen)) return 0; /* bad packet */ } - /* rewrite addresses in additional section too */ - if (!do_doctor(p, ntohs(header->arcount), header, qlen, doctored)) - return 0; - - if (!found_soa) - minttl = daemon->neg_ttl; - - return minttl; + return 0; } /* Print TXT reply to log */ -static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag) +static int log_txt(char *name, unsigned char *p, const int ardlen, int flag) { unsigned char *p1 = p; @@ -515,7 +616,7 @@ static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag) } *p3 = 0; - log_query(secflag | F_FORWARD, name, NULL, (char*)p1, 0); + log_query(flag, name, NULL, (char*)p1, 0); /* restore */ memmove(p1 + 1, p1, i); *p1 = len; @@ -533,10 +634,10 @@ static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag) */ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind, - int no_cache_dnssec, int secure, int *doctored) + int no_cache_dnssec, int secure) { unsigned char *p, *p1, *endrr, *namep; - int j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; + int j, qtype, qclass, aqtype, aqclass, ardlen, res; unsigned long ttl = 0; union all_addr addr; #ifdef HAVE_IPSET @@ -556,28 +657,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t int cname_short = 0; #endif unsigned long cttl = ULONG_MAX, attl; - + cache_start_insert(); - /* find_soa is needed for dns_doctor side effects, so don't call it lazily if there are any. */ - if (daemon->doctors || option_bool(OPT_DNSSEC_VALID)) - { - searched_soa = 1; - ttl = find_soa(header, qlen, doctored); - - if (*doctored) - { - if (secure) - return 0; -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID)) - for (j = 0; j < ntohs(header->ancount); j++) - if (daemon->rr_status[j] != 0) - return 0; -#endif - } - } - namep = p = (unsigned char *)(header+1); if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4)) @@ -626,7 +708,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR)) { #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0) + if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0) { /* validated RR anywhere in CNAME chain, don't cache. */ if (cname_short || aqtype == T_CNAME) @@ -674,17 +756,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!found && !option_bool(OPT_NO_NEG)) { - if (!searched_soa) - { - searched_soa = 1; - ttl = find_soa(header, qlen, doctored); - } + /* For reverse records, we use the name field to store the SOA name. */ + int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now); flags |= F_NEG | (secure ? F_DNSSECOK : 0); if (name_encoding && ttl) { flags |= F_REVERSE | name_encoding; - cache_insert(NULL, &addr, C_IN, now, ttl, flags); + if (!have_soa) + flags |= F_NO_RR; /* Marks no SOA found. */ + cache_insert(name + substring, &addr, C_IN, now, ttl, flags); } log_query(flags | F_UPSTREAM, name, &addr, NULL, 0); @@ -706,7 +787,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t addrlen = IN6ADDRSZ; flags |= F_IPV6; } - else if (qtype != T_CNAME && (qtype == T_SRV || rr_on_list(daemon->cache_rr, qtype))) + else if (qtype != T_CNAME && + (qtype == T_SRV || rr_on_list(daemon->cache_rr, qtype) || rr_on_list(daemon->cache_rr, T_ANY))) flags |= F_RR; else insert = 0; /* NOTE: do not cache data from CNAME queries. */ @@ -732,18 +814,19 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t } GETSHORT(ardlen, p1); endrr = p1+ardlen; + + if (!CHECK_LEN(header, endrr, qlen, 0)) + return 2; /* bad packet */ /* Not what we're looking for? */ if (aqclass != C_IN || res == 2) { p1 = endrr; - if (!CHECK_LEN(header, p1, qlen, 0)) - return 2; /* bad packet */ continue; } #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0) + if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0) { secflag = F_DNSSECOK; @@ -800,7 +883,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t found = 1; } - else if (aqtype != qtype) + else if (qtype == T_ANY || aqtype != qtype) { #ifdef HAVE_DNSSEC if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG) @@ -851,7 +934,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t { /* Copy the rest of the RR and end. */ if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1)) - return 0; + { + blockdata_free(addr.rrblock.rrdata); + return 0; + } addr.rrblock.datalen += endrr - p1; } else if (desc == 0) @@ -948,26 +1034,17 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t blockdata_free(addr.rrblock.rrdata); } + /* We're filtering this RRtype. It will be removed from the + returned packet in process_reply() but gets cached here anyway + and will be filtered again on the way out of the cache. Here, + we just need to alter the logging. */ + if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype)) + secflag = F_NEG | F_CONFIG; + if (aqtype == T_TXT) - { - if (!CHECK_LEN(header, p1, qlen, ardlen)) - return 2; - - log_txt(name, p1, ardlen, secflag | F_UPSTREAM); - } + log_txt(name, p1, ardlen, flags | F_FORWARD | F_UPSTREAM | secflag); else - { - int negflag = F_UPSTREAM; - - /* We're filtering this RRtype. It will be removed from the - returned packet in process_reply() but gets cached here anyway - and will be filtered again on the way out of the cache. Here, - we just need to alter the logging. */ - if (rr_on_list(daemon->filter_rr, qtype)) - negflag = F_NEG | F_CONFIG; - - log_query(negflag | flags | F_FORWARD | secflag, name, &addr, NULL, aqtype); - } + log_query(flags | F_FORWARD | F_UPSTREAM | secflag, name, &addr, NULL, aqtype); } p1 = endrr; @@ -987,22 +1064,24 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0); - if (!searched_soa) + if (insert && !option_bool(OPT_NO_NEG)) { - searched_soa = 1; - ttl = find_soa(header, qlen, doctored); - } - - /* If there's no SOA to get the TTL from, but there is a CNAME - pointing at this, inherit its TTL */ - if (insert && !option_bool(OPT_NO_NEG) && (ttl || cpp)) - { - if (ttl == 0) - ttl = cttl; + int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now); + + /* If there's no SOA to get the TTL from, but there is a CNAME + pointing at this, inherit its TTL */ + if (ttl || cpp) + { + if (!ttl) + ttl = cttl; + + addr.rrdata.datalen = substring; + addr.rrdata.rrtype = qtype; + + if (!have_soa) + flags |= F_NO_RR; /* Marks no SOA found. */ + } - if (flags & F_RR) - addr.rrdata.rrtype = qtype; - newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0)); if (newc && cpp) { @@ -1014,15 +1093,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t } } - if (header->hb3 & HB3_TC) - log_query(F_UPSTREAM, NULL, NULL, "truncated", 0); - - /* Don't put stuff from a truncated packet into the cache. - Don't cache replies from non-recursive nameservers, since we may get a + /* Don't cache replies from non-recursive nameservers, since we may get a reply containing a CNAME but not its target, even though the target does exist. */ - if (!(header->hb3 & HB3_TC) && - !(header->hb4 & HB4_CD) && + if (!(header->hb4 & HB4_CD) && (header->hb4 & HB4_RA) && !no_cache_dnssec) cache_end_insert(); @@ -1226,6 +1300,10 @@ int check_for_local_domain(char *name, time_t now) if (cache_find_non_terminal(name, now)) return 1; + if (is_name_synthetic(F_IPV4, name, NULL) || + is_name_synthetic(F_IPV6, name, NULL)) + return 1; + return 0; } @@ -1251,8 +1329,7 @@ static int check_bad_address(struct dns_header *header, size_t qlen, struct bogu GETSHORT(qtype, p); GETSHORT(qclass, p); GETLONG(ttl, p); - GETSHORT(rdlen, p); - + GETSHORT(rdlen, p) if (ttlp) *ttlp = ttl; @@ -1305,8 +1382,9 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, /* Found a bogus address. Insert that info here, since there no SOA record to get the ttl from in the normal processing */ cache_start_insert(); - cache_insert(name, NULL, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); + cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | F_NXDOMAIN); cache_end_insert(); + log_query(F_CONFIG | F_FORWARD | F_NEG | F_NXDOMAIN, name, NULL, NULL, 0); return 1; } @@ -1509,13 +1587,14 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, union all_addr addr; int nameoffset; unsigned short flag; - int q, ans, anscount = 0, addncount = 0; - struct crec *crecp; + int ans, anscount = 0, nscount = 0, addncount = 0; + struct crec *crecp, *soa_lookup = NULL; int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1; struct mx_srv_record *rec; size_t len; int rd_bit = (header->hb3 & HB3_RD); - + int count = 255; /* catch loops */ + if (stale) *stale = 0; @@ -1523,7 +1602,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, *filtered = 0; /* never answer queries with RD unset, to avoid cache snooping. */ - if (ntohs(header->ancount) != 0 || + if ( ntohs(header->qdcount) != 1 || + ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0 || ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY ) @@ -1543,425 +1623,429 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, /* now process each question, answers go in RRs after the question */ p = (unsigned char *)(header+1); - for (q = ntohs(header->qdcount); q != 0; q--) - { - int count = 255; /* catch loops */ - - /* save pointer to name for copying into answers */ - nameoffset = p - (unsigned char *)header; - - /* now extract name as .-concatenated string into name */ - if (!extract_name(header, qlen, &p, name, 1, 4)) - return 0; /* bad packet */ - - GETSHORT(qtype, p); - GETSHORT(qclass, p); - - ans = 0; /* have we answered this question */ - - if (qclass == C_IN) - while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME | F_NXDOMAIN))) + /* save pointer to name for copying into answers */ + nameoffset = p - (unsigned char *)header; + + /* now extract name as .-concatenated string into name */ + if (!extract_name(header, qlen, &p, name, 1, 4)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + ans = 0; /* have we answered this question */ + + if (qclass == C_IN) + while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME | F_NXDOMAIN))) + { + char *cname_target; + int stale_flag = 0; + + if (crec_isstale(crecp, now)) { - char *cname_target; - int stale_flag = 0; - - if (crec_isstale(crecp, now)) - { - if (stale) - *stale = 1; - - stale_flag = F_STALE; - } - - if (crecp->flags & F_NXDOMAIN) - { - if (qtype == T_CNAME) - { - log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); - auth = 0; - nxdomain = 1; - ans = 1; - } - break; - } - - cname_target = cache_get_cname_target(crecp); - - /* If the client asked for DNSSEC don't use cached data. */ - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || - (rd_bit && (!do_bit || cache_validated(crecp)))) - { - if (crecp->flags & F_CONFIG || qtype == T_CNAME) - ans = 1; - - if (!(crecp->flags & F_DNSSECOK)) - sec_data = 0; - - log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), &nameoffset, - T_CNAME, C_IN, "d", cname_target)) - anscount++; - } - else - return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */ + if (stale) + *stale = 1; + stale_flag = F_STALE; + } + + if (crecp->flags & F_NEG) + soa_lookup = crecp; + + if (crecp->flags & F_NXDOMAIN) + { if (qtype == T_CNAME) - break; + { + log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); + auth = 0; + nxdomain = 1; + ans = 1; + } + break; + } + + cname_target = cache_get_cname_target(crecp); + + /* If the client asked for DNSSEC don't use cached data. */ + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || + (rd_bit && (!do_bit || cache_validated(crecp)))) + { + if (crecp->flags & F_CONFIG || qtype == T_CNAME) + ans = 1; - strcpy(name, cname_target); + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_CNAME, C_IN, "d", cname_target)) + anscount++; + } + else + return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */ + + if (qtype == T_CNAME) + break; + + strcpy(name, cname_target); + } + + if (qtype == T_TXT || qtype == T_ANY) + { + struct txt_record *t; + for(t = daemon->txt; t ; t = t->next) + { + if (t->class == qclass && hostname_isequal(name, t->name)) + { + unsigned long ttl = daemon->local_ttl; + int ok = 1; + + ans = 1, sec_data = 0; +#ifndef NO_ID + /* Dynamically generate stat record */ + if (t->stat != 0) + { + ttl = 0; + if (!cache_make_stat(t)) + ok = 0; + } +#endif + if (ok) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + ttl, NULL, + T_TXT, t->class, "t", t->len, t->txt)) + anscount++; + } + } + } + } + + if (qclass == C_CHAOS) + { + /* don't forward *.bind and *.server chaos queries - always reply with NOTIMP */ + if (hostname_issubdomain("bind", name) || hostname_issubdomain("server", name)) + { + if (!ans) + { + notimp = 1, auth = 0; + + addr.log.rcode = NOTIMP; + log_query(F_CONFIG | F_RCODE, name, &addr, NULL, 0); + + ans = 1, sec_data = 0; + } + } + } + + if (qclass == C_IN) + { + struct txt_record *t; + + for (t = daemon->rr; t; t = t->next) + if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name)) + { + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_RRNAME, name, NULL, NULL, t->class); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + t->class, C_IN, "t", t->len, t->txt)) + anscount++; } - if (qtype == T_TXT || qtype == T_ANY) + if (qtype == T_PTR || qtype == T_ANY) { - struct txt_record *t; - for(t = daemon->txt; t ; t = t->next) - { - if (t->class == qclass && hostname_isequal(name, t->name)) - { - unsigned long ttl = daemon->local_ttl; - int ok = 1; - - ans = 1, sec_data = 0; -#ifndef NO_ID - /* Dynamically generate stat record */ - if (t->stat != 0) - { - ttl = 0; - if (!cache_make_stat(t)) - ok = 0; - } -#endif - if (ok) - { - log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - ttl, NULL, - T_TXT, t->class, "t", t->len, t->txt)) - anscount++; - } - } - } - } - - if (qclass == C_CHAOS) - { - /* don't forward *.bind and *.server chaos queries - always reply with NOTIMP */ - if (hostname_issubdomain("bind", name) || hostname_issubdomain("server", name)) - { - if (!ans) - { - notimp = 1, auth = 0; - - addr.log.rcode = NOTIMP; - log_query(F_CONFIG | F_RCODE, name, &addr, NULL, 0); - - ans = 1, sec_data = 0; - } - } - } - - if (qclass == C_IN) - { - struct txt_record *t; - - for (t = daemon->rr; t; t = t->next) - if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name)) + /* see if it's w.z.y.z.in-addr.arpa format */ + int is_arpa = in_arpa_name_2_addr(name, &addr); + struct ptr_record *ptr; + struct interface_name* intr = NULL; + + for (ptr = daemon->ptr; ptr; ptr = ptr->next) + if (hostname_isequal(name, ptr->name)) + break; + + if (is_arpa == F_IPV4) + for (intr = daemon->int_names; intr; intr = intr->next) { - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_RRNAME, name, NULL, NULL, t->class); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, - t->class, C_IN, "t", t->len, t->txt)) - anscount++; + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr) + break; + + if (addrlist) + break; + else if (!(intr->flags & INP4)) + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } + else if (is_arpa == F_IPV6) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6)) + break; + + if (addrlist) + break; + else if (!(intr->flags & INP6)) + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; } - if (qtype == T_PTR || qtype == T_ANY) + if (intr) { - /* see if it's w.z.y.z.in-addr.arpa format */ - int is_arpa = in_arpa_name_2_addr(name, &addr); - struct ptr_record *ptr; - struct interface_name* intr = NULL; - + sec_data = 0; + ans = 1; + log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL, 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", intr->name)) + anscount++; + } + else if (ptr) + { + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); for (ptr = daemon->ptr; ptr; ptr = ptr->next) - if (hostname_isequal(name, ptr->name)) - break; - - if (is_arpa == F_IPV4) - for (intr = daemon->int_names; intr; intr = intr->next) - { - struct addrlist *addrlist; - - for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) - if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr) - break; - - if (addrlist) - break; - else if (!(intr->flags & INP4)) - while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) - intr = intr->next; - } - else if (is_arpa == F_IPV6) - for (intr = daemon->int_names; intr; intr = intr->next) - { - struct addrlist *addrlist; - - for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) - if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6)) - break; - - if (addrlist) - break; - else if (!(intr->flags & INP6)) - while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) - intr = intr->next; - } + if (hostname_isequal(name, ptr->name) && + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", ptr->ptr)) + anscount++; - if (intr) + } + else if (is_arpa && (crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) + { + /* Don't use cache when DNSSEC data required, unless we know that + the zone is unsigned, which implies that we're doing + validation. */ + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || + (rd_bit && (!do_bit || cache_validated(crecp)) )) { - sec_data = 0; - ans = 1; - log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL, 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, - T_PTR, C_IN, "d", intr->name)) - anscount++; - } - else if (ptr) - { - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); - for (ptr = daemon->ptr; ptr; ptr = ptr->next) - if (hostname_isequal(name, ptr->name) && - add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, - T_PTR, C_IN, "d", ptr->ptr)) - anscount++; - - } - else if (is_arpa && (crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) - { - /* Don't use cache when DNSSEC data required, unless we know that - the zone is unsigned, which implies that we're doing - validation. */ - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || - (rd_bit && (!do_bit || cache_validated(crecp)) )) - { - do - { - int stale_flag = 0; + do + { + int stale_flag = 0; + + if (crec_isstale(crecp, now)) + { + if (stale) + *stale = 1; - if (crec_isstale(crecp, now)) - { - if (stale) - *stale = 1; - - stale_flag = F_STALE; - } + stale_flag = F_STALE; + } + + /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) + continue; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + ans = 1; + + if (crecp->flags & F_NEG) + { + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0); + soa_lookup = crecp; + } + else + { + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; - /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - continue; - - if (!(crecp->flags & F_DNSSECOK)) - sec_data = 0; + log_query(stale_flag | (crecp->flags & ~F_FORWARD), cache_get_name(crecp), &addr, + record_source(crecp->uid), 0); - ans = 1; - - if (crecp->flags & F_NEG) - { - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; - log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0); - } - else - { - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - - log_query(stale_flag | (crecp->flags & ~F_FORWARD), cache_get_name(crecp), &addr, - record_source(crecp->uid), 0); - - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), NULL, - T_PTR, C_IN, "d", cache_get_name(crecp))) - anscount++; - } - } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); - } - } - else if (is_rev_synth(is_arpa, &addr, name)) - { - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL, 0); - - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, - T_PTR, C_IN, "d", name)) - anscount++; - } - else if (option_bool(OPT_BOGUSPRIV) && - ((is_arpa == F_IPV6 && private_net6(&addr.addr6, 1)) || (is_arpa == F_IPV4 && private_net(addr.addr4, 1))) && - !lookup_domain(name, F_DOMAINSRV, NULL, NULL)) - { - /* if no configured server, not in cache, enabled and private IPV4 address, return NXDOMAIN */ - ans = 1; - sec_data = 0; - nxdomain = 1; - log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN, - name, &addr, NULL, 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, + T_PTR, C_IN, "d", cache_get_name(crecp))) + anscount++; + } + } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); } } - - for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0) + else if (is_rev_synth(is_arpa, &addr, name)) { - unsigned short type = (flag == F_IPV6) ? T_AAAA : T_A; - struct interface_name *intr; - - if (qtype != type && qtype != T_ANY) - continue; + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL, 0); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", name)) + anscount++; + } + else if (option_bool(OPT_BOGUSPRIV) && + ((is_arpa == F_IPV6 && private_net6(&addr.addr6, 1)) || (is_arpa == F_IPV4 && private_net(addr.addr4, 1))) && + !lookup_domain(name, F_DOMAINSRV, NULL, NULL)) + { + /* if no configured server, not in cache, enabled and private IPV4 address, return NXDOMAIN */ + ans = 1; + sec_data = 0; + nxdomain = 1; + log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN, + name, &addr, NULL, 0); + } + } + + for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0) + { + unsigned short type = (flag == F_IPV6) ? T_AAAA : T_A; + struct interface_name *intr; + + if (qtype != type && qtype != T_ANY) + continue; + + /* interface name stuff */ + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + break; + + if (intr) + { + struct addrlist *addrlist; + int gotit = 0, localise = 0; + + enumerate_interfaces(0); + + /* See if a putative address is on the network from which we received + the query, is so we'll filter other answers. */ + if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && type == T_A) + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (!(addrlist->flags & ADDRLIST_IPV6) && + is_same_net(addrlist->addr.addr4, local_addr, local_netmask)) + { + localise = 1; + break; + } - /* interface name stuff */ for (intr = daemon->int_names; intr; intr = intr->next) if (hostname_isequal(name, intr->name)) - break; - - if (intr) - { - struct addrlist *addrlist; - int gotit = 0, localise = 0; - - enumerate_interfaces(0); - - /* See if a putative address is on the network from which we received - the query, is so we'll filter other answers. */ - if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && type == T_A) - for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(name, intr->name)) - for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) - if (!(addrlist->flags & ADDRLIST_IPV6) && - is_same_net(addrlist->addr.addr4, local_addr, local_netmask)) - { - localise = 1; - break; - } - - for (intr = daemon->int_names; intr; intr = intr->next) - if (hostname_isequal(name, intr->name)) - { - for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) - if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type) - { - if (localise && - !is_same_net(addrlist->addr.addr4, local_addr, local_netmask)) - continue; - - if (addrlist->flags & ADDRLIST_REVONLY) - continue; - - ans = 1; - sec_data = 0; - gotit = 1; - log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL, 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, type, C_IN, - type == T_A ? "4" : "6", &addrlist->addr)) - anscount++; - } - } - - if (!gotit) - log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL, 0); - - continue; - } - - if ((crecp = cache_find_by_name(NULL, name, now, flag | F_NXDOMAIN))) - { - int localise = 0; - - /* See if a putative address is on the network from which we received - the query, is so we'll filter other answers. */ - if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4) - { - struct crec *save = crecp; - do { - if ((crecp->flags & F_HOSTS) && - is_same_net(crecp->addr.addr4, local_addr, local_netmask)) - { - localise = 1; - break; - } - } while ((crecp = cache_find_by_name(crecp, name, now, flag))); - crecp = save; - } - - /* If the client asked for DNSSEC don't use cached data. */ - if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || - (rd_bit && (!do_bit || cache_validated(crecp)) )) - do - { - int stale_flag = 0; - - if (crec_isstale(crecp, now)) - { - if (stale) - *stale = 1; - - stale_flag = F_STALE; - } - - /* don't answer wildcard queries with data not from /etc/hosts - or DHCP leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) - break; - - if (!(crecp->flags & F_DNSSECOK)) + { + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type) + { + if (localise && + !is_same_net(addrlist->addr.addr4, local_addr, local_netmask)) + continue; + + if (addrlist->flags & ADDRLIST_REVONLY) + continue; + + ans = 1; sec_data = 0; - - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - - if (rr_on_list(daemon->filter_rr, qtype) && - !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG))) - { - /* We have a cached answer but we're filtering it. */ - ans = 1; - sec_data = 0; - - log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); - - if (filtered) - *filtered = 1; - } - else if (crecp->flags & F_NEG) + gotit = 1; + log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL, 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, + type == T_A ? "4" : "6", &addrlist->addr)) + anscount++; + } + } + + if (!gotit) + log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL, 0); + + continue; + } + + if ((crecp = cache_find_by_name(NULL, name, now, flag))) + { + int localise = 0; + + /* See if a putative address is on the network from which we received + the query, is so we'll filter other answers. */ + if (!(crecp->flags & F_NEG) && local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4) + { + struct crec *save = crecp; + do { + if ((crecp->flags & F_HOSTS) && + is_same_net(crecp->addr.addr4, local_addr, local_netmask)) + { + localise = 1; + break; + } + } while ((crecp = cache_find_by_name(crecp, name, now, flag))); + crecp = save; + } + + /* If the client asked for DNSSEC don't use cached data. */ + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || + (rd_bit && (!do_bit || cache_validated(crecp)) )) + do + { + int stale_flag = 0; + + if (crec_isstale(crecp, now)) + { + if (stale) + *stale = 1; + + stale_flag = F_STALE; + } + + /* don't answer wildcard queries with data not from /etc/hosts + or DHCP leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + break; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + + if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype) && + !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG))) + { + /* We have a cached answer but we're filtering it. */ + ans = 1; + sec_data = 0; + + log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); + + if (filtered) + *filtered = 1; + } + else if (crecp->flags & F_NEG) + { + if (qtype != T_ANY) { ans = 1; auth = 0; + soa_lookup = crecp; if (crecp->flags & F_NXDOMAIN) nxdomain = 1; - // Pi-hole modification: Added record_source(crecp->uid) such that the subroutines know - // where the reply came from (e.g. gravity.list) + // Pi-hole modification: Added record_source(crecp->uid) such that the subroutines know + // where the reply came from (e.g. gravity.list) log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); } - else - { - /* If we are returning local answers depending on network, - filter here. */ - if (localise && - (crecp->flags & F_HOSTS) && - !is_same_net(crecp->addr.addr4, local_addr, local_netmask)) - continue; - - ans = 1; - log_query(stale_flag | (crecp->flags & ~F_REVERSE), name, &crecp->addr, - record_source(crecp->uid), 0); + } + else + { + /* If we are returning local answers depending on network, + filter here. */ + if (localise && + (crecp->flags & F_HOSTS) && + !is_same_net(crecp->addr.addr4, local_addr, local_netmask)) + continue; + + ans = 1; + log_query(stale_flag | (crecp->flags & ~F_REVERSE), name, &crecp->addr, + record_source(crecp->uid), 0); // ****************************** Pi-hole modification ****************************** const char *src = crecp != NULL ? crecp->flags & F_BIGNAME ? crecp->name.bname->name : crecp->name.sname : NULL; if(FTL_CNAME(name, src, daemon->log_display_id)) @@ -1975,225 +2059,262 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, break; } // ********************************************************************************** - - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), NULL, type, C_IN, - type == T_A ? "4" : "6", &crecp->addr)) - anscount++; - } - } while ((crecp = cache_find_by_name(crecp, name, now, flag))); - } - else if (is_name_synthetic(flag, name, &addr)) - { - ans = 1, sec_data = 0; - log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL, 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr)) - anscount++; - } - } - - if (qtype == T_MX || qtype == T_ANY) - { - int found = 0; - for (rec = daemon->mxnames; rec; rec = rec->next) - if (!rec->issrv && hostname_isequal(name, rec->name)) - { - int offset; - - ans = found = 1; - sec_data = 0; - - log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, - &offset, T_MX, C_IN, "sd", rec->weight, rec->target)) - { - anscount++; - if (rec->target) - rec->offset = offset; + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, type, C_IN, + type == T_A ? "4" : "6", &crecp->addr)) + anscount++; } - } - - if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && - cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP | F_NO_RR)) - { - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, - T_MX, C_IN, "sd", 1, - option_bool(OPT_SELFMX) ? name : daemon->mxtarget)) - anscount++; + } while ((crecp = cache_find_by_name(crecp, name, now, flag))); } - } - - if (qtype == T_SRV || qtype == T_ANY) + else if (is_name_synthetic(flag, name, &addr)) { - struct mx_srv_record *move = NULL, **up = &daemon->mxnames; - - for (rec = daemon->mxnames; rec; rec = rec->next) - if (rec->issrv && hostname_isequal(name, rec->name)) - { - int offset; - - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, - &offset, T_SRV, C_IN, "sssd", - rec->priority, rec->weight, rec->srvport, rec->target)) - { - anscount++; - if (rec->target) - rec->offset = offset; - } - - /* unlink first SRV record found */ - if (!move) - { - move = rec; - *up = rec->next; - } - else - up = &rec->next; - } - else - up = &rec->next; - - /* put first SRV record back at the end. */ - if (move) - { - *up = move; - move->next = NULL; - } - } - - if (qtype == T_NAPTR || qtype == T_ANY) - { - struct naptr *na; - for (na = daemon->naptr; na; na = na->next) - if (hostname_isequal(name, na->name)) - { - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, - NULL, T_NAPTR, C_IN, "sszzzd", - na->order, na->pref, na->flags, na->services, na->regexp, na->replace)) - anscount++; - } - } - - if (qtype == T_MAILB) - ans = 1, nxdomain = 1, sec_data = 0; - - if (qtype == T_SOA && option_bool(OPT_FILTER)) - { - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_NEG, name, &addr, NULL, 0); - } - - if (!ans) - { - if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) && - rd_bit && (!do_bit || cache_validated(crecp))) - do - { - int flags = crecp->flags; - unsigned short rrtype; - - if (flags & F_KEYTAG) - rrtype = crecp->addr.rrblock.rrtype; - else - rrtype = crecp->addr.rrdata.rrtype; - - if ((flags & F_NXDOMAIN) || rrtype == qtype) - { - char *rrdata = NULL; - unsigned short rrlen = 0; - - if (crec_isstale(crecp, now)) - { - if (stale) - *stale = 1; - - flags |= F_STALE; - } - - if (!(flags & F_DNSSECOK)) - sec_data = 0; - - if (flags & F_NXDOMAIN) - nxdomain = 1; - else if (rr_on_list(daemon->filter_rr, qtype)) - flags |= F_NEG | F_CONFIG; - - auth = 0; - ans = 1; - - if (!(flags & F_NEG)) - { - if (flags & F_KEYTAG) - { - rrlen = crecp->addr.rrblock.datalen; - rrdata = blockdata_retrieve(crecp->addr.rrblock.rrdata, crecp->addr.rrblock.datalen, NULL); - } - else - { - rrlen = crecp->addr.rrdata.datalen; - rrdata = crecp->addr.rrdata.data; - } - } - - if (!(flags & F_NEG) && add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), NULL, qtype, C_IN, "t", - rrlen, rrdata)) - anscount++; - - /* log after cache insertion as log_txt mangles rrdata */ - if (qtype == T_TXT && !(crecp->flags & F_NEG)) - log_txt(name, (unsigned char *)rrdata, rrlen, crecp->flags & F_DNSSECOK); - else - log_query(flags, name, &crecp->addr, NULL, 0); - } - } while ((crecp = cache_find_by_name(crecp, name, now, F_RR))); - } - - if (!ans && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_')))) - { - ans = 1; - sec_data = 0; - log_query(F_CONFIG | F_NEG, name, NULL, NULL, 0); - } - - - if (!ans && rr_on_list(daemon->filter_rr, qtype)) - { - /* We don't have a cached answer and when we get an answer from upstream we're going to - filter it anyway. If we have a cached answer for the domain for another RRtype then - that may be enough to tell us if the answer should be NODATA and save the round trip. - Cached NXDOMAIN has already been handled, so here we look for any record for the domain, - since its existence allows us to return a NODATA answer. Note that we never set the AD flag, - since we didn't authenticate the record. */ - - if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_RR | F_CNAME)) - { - ans = 1; - sec_data = auth = 0; - - log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); - - if (filtered) - *filtered = 1; - } + ans = 1, sec_data = 0; + log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL, 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr)) + anscount++; } } + if (qtype == T_MX || qtype == T_ANY) + { + int found = 0; + for (rec = daemon->mxnames; rec; rec = rec->next) + if (!rec->issrv && hostname_isequal(name, rec->name)) + { + int offset; + + ans = found = 1; + sec_data = 0; + + log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, + &offset, T_MX, C_IN, "sd", rec->weight, rec->target)) + { + anscount++; + if (rec->target) + rec->offset = offset; + } + } + + if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && + cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP | F_NO_RR)) + { + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, + T_MX, C_IN, "sd", 1, + option_bool(OPT_SELFMX) ? name : daemon->mxtarget)) + anscount++; + } + } + + if (qtype == T_SRV || qtype == T_ANY) + { + struct mx_srv_record *move = NULL, **up = &daemon->mxnames; + + for (rec = daemon->mxnames; rec; rec = rec->next) + if (rec->issrv && hostname_isequal(name, rec->name)) + { + int offset; + + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, + &offset, T_SRV, C_IN, "sssd", + rec->priority, rec->weight, rec->srvport, rec->target)) + { + anscount++; + if (rec->target) + rec->offset = offset; + } + + /* unlink first SRV record found */ + if (!move) + { + move = rec; + *up = rec->next; + } + else + up = &rec->next; + } + else + up = &rec->next; + + /* put first SRV record back at the end. */ + if (move) + { + *up = move; + move->next = NULL; + } + } + + if (qtype == T_NAPTR || qtype == T_ANY) + { + struct naptr *na; + for (na = daemon->naptr; na; na = na->next) + if (hostname_isequal(name, na->name)) + { + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_RRNAME, name, NULL, "", 0); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, + NULL, T_NAPTR, C_IN, "sszzzd", + na->order, na->pref, na->flags, na->services, na->regexp, na->replace)) + anscount++; + } + } + + if (qtype == T_MAILB) + ans = 1, nxdomain = 1, sec_data = 0; + + if (qtype == T_SOA && option_bool(OPT_FILTER)) + { + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_NEG, name, &addr, NULL, 0); + } + if (!ans) - return 0; /* failed to answer a question */ + { + if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) && + rd_bit && (!do_bit || cache_validated(crecp))) + do + { + int flags = crecp->flags; + unsigned short rrtype; + + if (flags & F_KEYTAG) + rrtype = crecp->addr.rrblock.rrtype; + else + rrtype = crecp->addr.rrdata.rrtype; + + if ((flags & F_NXDOMAIN) || rrtype == qtype) + { + char *rrdata = NULL; + unsigned short rrlen = 0; + + if (crec_isstale(crecp, now)) + { + if (stale) + *stale = 1; + + flags |= F_STALE; + } + + if (!(flags & F_DNSSECOK)) + sec_data = 0; + + if (flags & F_NXDOMAIN) + nxdomain = 1; + else if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype)) + flags |= F_NEG | F_CONFIG; + + auth = 0; + ans = 1; + + if (flags & F_NEG) + soa_lookup = crecp; + + if (!(flags & F_NEG)) + { + if (flags & F_KEYTAG) + { + rrlen = crecp->addr.rrblock.datalen; + rrdata = blockdata_retrieve(crecp->addr.rrblock.rrdata, crecp->addr.rrblock.datalen, NULL); + } + else + { + rrlen = crecp->addr.rrdata.datalen; + rrdata = crecp->addr.rrdata.data; + } + } + + if (!(flags & F_NEG) && add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, qtype, C_IN, "t", + rrlen, rrdata)) + anscount++; + + /* log after cache insertion as log_txt mangles rrdata */ + if (qtype == T_TXT && !(crecp->flags & F_NEG)) + log_txt(name, (unsigned char *)rrdata, rrlen, crecp->flags & F_DNSSECOK); + else + log_query(flags, name, &crecp->addr, NULL, 0); + } + } while ((crecp = cache_find_by_name(crecp, name, now, F_RR))); + } + + if (!ans && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_')))) + { + ans = 1; + sec_data = 0; + log_query(F_CONFIG | F_NEG, name, NULL, NULL, 0); + } + + + if (qtype != T_ANY && !ans && rr_on_list(daemon->filter_rr, qtype)) + { + /* We don't have a cached answer and when we get an answer from upstream we're going to + filter it anyway. If we have a cached answer for the domain for another RRtype then + that may be enough to tell us if the answer should be NODATA and save the round trip. + Cached NXDOMAIN has already been handled, so here we look for any record for the domain, + since its existence allows us to return a NODATA answer. Note that we never set the AD flag, + since we didn't authenticate the record. */ + + if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_RR | F_CNAME)) + { + ans = 1; + sec_data = auth = 0; + + log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); + + if (filtered) + *filtered = 1; + } + } } + if (!ans) + return 0; /* failed to answer a question */ + + /* We found a negative record. See if we have an SOA record to + return in the AUTH section. + + For FORWARD NEG records, the addr.rrdata.datalen field of the othewise + empty addr is used to held an offset in to the name which yields the SOA + name. For REVERSE NEG records, the otherwise empty name field holds the + SOA name. If soa_name has zero length, then no SOA is known. soa_lookup + MUST be a neg record here. + + If the F_NO_RR flag is set, there was no SOA record supplied with the RR. */ + if (soa_lookup && !(soa_lookup->flags & F_NO_RR)) + { + char *soa_name = soa_lookup->flags & F_REVERSE ? cache_get_name(soa_lookup) : name + soa_lookup->addr.rrdata.datalen; + + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, soa_name, now, F_RR))) + if (crecp->addr.rrblock.rrtype == T_SOA) + { + char *rrdata; + + if (!(crecp->flags & F_NEG) && + (rrdata = blockdata_retrieve(crecp->addr.rrblock.rrdata, crecp->addr.rrblock.datalen, NULL)) && + add_resource_record(header, limit, &trunc, 0, &ansp, + crec_ttl(crecp, now), NULL, T_SOA, C_IN, "t", + soa_name, crecp->addr.rrblock.datalen, rrdata)) + { + nscount++; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + } + break; + } + } + /* create an additional data section, for stuff in SRV and MX record replies. */ for (rec = daemon->mxnames; rec; rec = rec->next) if (rec->offset != 0) @@ -2215,7 +2336,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (add_resource_record(header, limit, NULL, rec->offset, &ansp, crec_ttl(crecp, now), NULL, type, C_IN, crecp->flags & F_IPV4 ? "4" : "6", &crecp->addr)) - addncount++; + { + addncount++; + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + } } } @@ -2240,7 +2365,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, else SET_RCODE(header, NOERROR); /* no error */ header->ancount = htons(anscount); - header->nscount = htons(0); + header->nscount = htons(nscount); header->arcount = htons(addncount); len = ansp - (unsigned char *)header; diff --git a/src/dnsmasq/rfc2131.c b/src/dnsmasq/rfc2131.c index 962b1937..42d148a0 100644 --- a/src/dnsmasq/rfc2131.c +++ b/src/dnsmasq/rfc2131.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/rfc3315.c b/src/dnsmasq/rfc3315.c index bd448c7b..400d9396 100644 --- a/src/dnsmasq/rfc3315.c +++ b/src/dnsmasq/rfc3315.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/rrfilter.c b/src/dnsmasq/rrfilter.c index 8857d7bc..33d385c4 100644 --- a/src/dnsmasq/rrfilter.c +++ b/src/dnsmasq/rrfilter.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -213,6 +213,14 @@ size_t rrfilter(struct dns_header *header, size_t *plen, int mode) if (i < ntohs(header->ancount) && type == qtype && class == qclass) continue; } + else if (qtype == T_ANY && rr_on_list(daemon->filter_rr, T_ANY)) + { + /* Filter replies to ANY queries in the spirit of + RFC RFC 8482 para 4.3 */ + if (class != C_IN || + type == T_A || type == T_AAAA || type == T_MX || type == T_CNAME) + continue; + } else { /* Only looking at answer section now. */ diff --git a/src/dnsmasq/slaac.c b/src/dnsmasq/slaac.c index 4f06181b..c37e7ff2 100644 --- a/src/dnsmasq/slaac.c +++ b/src/dnsmasq/slaac.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/tftp.c b/src/dnsmasq/tftp.c index f0362971..4421cf93 100644 --- a/src/dnsmasq/tftp.c +++ b/src/dnsmasq/tftp.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -585,8 +585,13 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *c void check_tftp_listeners(time_t now) { + struct listener *listener; struct tftp_transfer *transfer, *tmp, **up; + for (listener = daemon->listeners; listener; listener = listener->next) + if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN)) + tftp_request(listener, now); + /* In single port mode, all packets come via port 69 and tftp_request() */ if (!option_bool(OPT_SINGLE_PORT)) for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) diff --git a/src/dnsmasq/ubus.c b/src/dnsmasq/ubus.c index cdf79aa5..a5758e77 100644 --- a/src/dnsmasq/ubus.c +++ b/src/dnsmasq/ubus.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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/util.c b/src/dnsmasq/util.c index 6521e3f1..0c7de444 100644 --- a/src/dnsmasq/util.c +++ b/src/dnsmasq/util.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2023 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2024 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 @@ -119,7 +119,7 @@ int rr_on_list(struct rrlist *list, unsigned short rr) { while (list) { - if (list->rr == rr || list->rr == T_ANY) + if (list->rr == rr) return 1; list = list->next; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 30bc7c05..4cda8910 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -2426,6 +2426,17 @@ static void FTL_dnssec(const char *arg, const union all_addr *addr, const int id else log_warn("Unknown DNSSEC status \"%s\"", arg); + // Set reply to NONE (if not already set) as we will not reply to this + // query when the status is neither SECURE nor INSECURE + if (query->reply == REPLY_UNKNOWN && + query->dnssec != DNSSEC_SECURE && + query->dnssec != DNSSEC_INSECURE) + { + struct timeval response; + gettimeofday(&response, 0); + query_set_reply(0, REPLY_NONE, addr, query, response); + } + // Mark query for updating in the database query->flags.database.changed = true; @@ -3489,3 +3500,9 @@ static const char *check_dnsmasq_name(const char *name) // else return name; } + +void get_dnsmasq_metrics_obj(cJSON *json) +{ + for (unsigned int i = 0; i < __METRIC_MAX; i++) + cJSON_AddNumberToObject(json, get_metric_name(i), daemon->metrics[i]); +} \ No newline at end of file diff --git a/src/metrics.h b/src/metrics.h index 1201cc34..da54a66c 100644 --- a/src/metrics.h +++ b/src/metrics.h @@ -10,6 +10,8 @@ #ifndef METRICS_H #define METRICS_H +#include "webserver/cJSON/cJSON.h" + // defined in src/dnsmasq/cache.c const char *rrtype_name(unsigned short type); @@ -88,4 +90,6 @@ struct metrics void get_dnsmasq_metrics(struct metrics *ci); +void get_dnsmasq_metrics_obj(cJSON *json); + #endif // METRICS_H diff --git a/test/dnsmasq_warnings b/test/dnsmasq_warnings index 43f579f5..2e31b3d9 100644 --- a/test/dnsmasq_warnings +++ b/test/dnsmasq_warnings @@ -78,6 +78,8 @@ src/dnsmasq/dnsmasq.c my_syslog(LOG_WARNING, _("failed to access %s: %s"), res->name, strerror(errno)); src/dnsmasq/dnsmasq.c my_syslog(LOG_WARNING, _("no servers found in %s, will retry"), latest->name); +src/dnsmasq/dnssec.c + my_syslog(LOG_WARNING, "limit exceeded: %s", message ? message : _("per-query crypto work")); src/dnsmasq/dnssec.c my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name); src/dnsmasq/forward.c @@ -85,11 +87,21 @@ src/dnsmasq/forward.c src/dnsmasq/forward.c my_syslog(LOG_WARNING, _("nameserver %s refused to do a recursive query"), daemon->namebuff); src/dnsmasq/forward.c - my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); + my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); +src/dnsmasq/forward.c + my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries")); +src/dnsmasq/forward.c + my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."), + daemon->namebuff[0] ? daemon->namebuff : "."); src/dnsmasq/forward.c my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ); src/dnsmasq/forward.c my_syslog(LOG_WARNING, _("ignoring query from non-local network %s (logged only once)"), daemon->addrbuff); +src/dnsmasq/forward.c + my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries")); +src/dnsmasq/forward.c + my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."), + daemon->namebuff[0] ? daemon->namebuff : "."); src/dnsmasq/forward.c my_syslog(LOG_WARNING, _("ignoring query from non-local network %s"), daemon->addrbuff); src/dnsmasq/forward.c @@ -108,7 +120,7 @@ src/dnsmasq/lease.c src/dnsmasq/log.c my_syslog(LOG_WARNING, _("overflow: %d log entries lost"), e); src/dnsmasq/network.c - my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno)); + my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno)); src/dnsmasq/network.c my_syslog(LOG_WARNING, _("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"), diff --git a/test/pdns/setup.sh b/test/pdns/setup.sh index dd97af47..715d5571 100644 --- a/test/pdns/setup.sh +++ b/test/pdns/setup.sh @@ -114,6 +114,9 @@ pdnsutil add-record ftl. regex-multiple AAAA fe80::3f41 pdnsutil add-record ftl. regex-notMultiple A 192.168.3.12 pdnsutil add-record ftl. regex-notMultiple AAAA fe80::3f41 +# TXT +pdnsutil add-record ftl. any TXT "\"Some example text\"" + # Create reverse lookup zone pdnsutil create-zone arpa ns1.ftl pdnsutil add-record arpa. 1.1.168.192.in-addr PTR ftl. diff --git a/test/test_suite.bats b/test/test_suite.bats index 121a2308..78cc84f0 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -311,6 +311,8 @@ printf "%s\n" "${lines[@]}" [[ ${lines[@]} == *"192.168.3.1"* ]] [[ ${lines[@]} == *"fe80::3c01"* ]] + # TXT records should not be returned due to filter-rr=ANY + [[ ${lines[@]} != *"Some example text"* ]] } @test "Local DNS test: CNAME cname-ok.ftl" { @@ -1442,6 +1444,20 @@ [[ ${lines[@]} == *"block"* ]] } +@test "API: No UNKNOWN reply in API" { + run bash -c 'curl -s 127.0.0.1/api/queries?reply=UNKNOWN | jq .queries' + printf "%s\n" "${lines[@]}" + run bash -c 'curl -s 127.0.0.1/api/queries?reply=UNKNOWN | jq ".queries | length"' + [[ ${lines[0]} == "0" ]] +} + +@test "API: No UNKNOWN status in API" { + run bash -c 'curl -s 127.0.0.1/api/queries?status=UNKNOWN | jq .queries' + printf "%s\n" "${lines[@]}" + run bash -c 'curl -s 127.0.0.1/api/queries?status=UNKNOWN | jq ".queries | length"' + [[ ${lines[0]} == "0" ]] +} + @test "API authorization (without password): No login required" { run bash -c 'curl -s 127.0.0.1/api/auth' printf "%s\n" "${lines[@]}"