Version in base suite: 4.9.14-0+deb13u1 Base version: pdns_4.9.14-0+deb13u1 Target version: pdns_4.9.15-0+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/pdns/pdns_4.9.14-0+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/pdns/pdns_4.9.15-0+deb13u1.dsc configure | 20 +-- configure.ac | 2 debian/changelog | 7 + docs/calidns.1 | 2 docs/dnsbulktest.1 | 2 docs/dnsgram.1 | 2 docs/dnspcap2calidns.1 | 2 docs/dnspcap2protobuf.1 | 2 docs/dnsreplay.1 | 2 docs/dnsscan.1 | 2 docs/dnsscope.1 | 2 docs/dnstcpbench.1 | 2 docs/dnswasher.1 | 2 docs/dumresp.1 | 2 docs/ixfrdist.1 | 2 docs/ixfrdist.yml.5 | 2 docs/ixplore.1 | 2 docs/nproxy.1 | 2 docs/nsec3dig.1 | 2 docs/pdns_control.1 | 2 docs/pdns_notify.1 | 2 docs/pdns_server.1 | 2 docs/pdnsutil.1 | 2 docs/saxfr.1 | 2 docs/sdig.1 | 2 docs/zone2json.1 | 2 docs/zone2ldap.1 | 2 docs/zone2sql.1 | 2 ext/yahttp/yahttp/reqresp.cpp | 11 + ext/yahttp/yahttp/reqresp.hpp | 16 +- modules/bindbackend/bindbackend2.cc | 37 +++++- pdns/auth-catalogzone.cc | 24 +++ pdns/auth-main.cc | 4 pdns/auth-secondarycommunicator.cc | 2 pdns/axfr-retriever.cc | 58 ++++----- pdns/comfun.cc | 7 - pdns/dns_random.hh | 5 pdns/dnspacket.cc | 97 +++++++++------ pdns/dnssecinfra.cc | 1 pdns/dnswriter.cc | 24 ++- pdns/dnswriter.hh | 6 pdns/dynlistener.cc | 2 pdns/gss_context.cc | 221 ++++++++++++++++++++++-------------- pdns/gss_context.hh | 11 + pdns/lua-record.cc | 27 ++-- pdns/misc.cc | 67 ++++------ pdns/misc.hh | 2 pdns/packethandler.cc | 45 ++----- pdns/packethandler.hh | 3 pdns/resolver.cc | 75 +++++++----- pdns/rfc2136handler.cc | 21 ++- pdns/stubresolver.cc | 30 ++-- pdns/webserver.cc | 7 - pdns/ws-auth.cc | 115 ++++++++++++------ pdns/ws-auth.hh | 4 55 files changed, 601 insertions(+), 400 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp_1m4pk87/pdns_4.9.14-0+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp_1m4pk87/pdns_4.9.15-0+deb13u1.dsc: no acceptable signature found diff -Nru pdns-4.9.14/configure pdns-4.9.15/configure --- pdns-4.9.14/configure 2026-04-08 09:58:43.000000000 +0000 +++ pdns-4.9.15/configure 2026-05-11 09:09:13.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for pdns 4.9.14. +# Generated by GNU Autoconf 2.71 for pdns 4.9.15. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, @@ -618,8 +618,8 @@ # Identity of this package. PACKAGE_NAME='pdns' PACKAGE_TARNAME='pdns' -PACKAGE_VERSION='4.9.14' -PACKAGE_STRING='pdns 4.9.14' +PACKAGE_VERSION='4.9.15' +PACKAGE_STRING='pdns 4.9.15' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1698,7 +1698,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures pdns 4.9.14 to adapt to many kinds of systems. +\`configure' configures pdns 4.9.15 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1769,7 +1769,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of pdns 4.9.14:";; + short | recursive ) echo "Configuration of pdns 4.9.15:";; esac cat <<\_ACEOF @@ -2040,7 +2040,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -pdns configure 4.9.14 +pdns configure 4.9.15 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2529,7 +2529,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by pdns $as_me 4.9.14, which was +It was created by pdns $as_me 4.9.15, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -4027,7 +4027,7 @@ # Define the identity of the package. PACKAGE='pdns' - VERSION='4.9.14' + VERSION='4.9.15' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -32469,7 +32469,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by pdns $as_me 4.9.14, which was +This file was extended by pdns $as_me 4.9.15, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -32537,7 +32537,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -pdns config.status 4.9.14 +pdns config.status 4.9.15 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff -Nru pdns-4.9.14/configure.ac pdns-4.9.15/configure.ac --- pdns-4.9.14/configure.ac 2026-04-08 09:58:31.000000000 +0000 +++ pdns-4.9.15/configure.ac 2026-05-11 09:09:03.000000000 +0000 @@ -1,6 +1,6 @@ AC_PREREQ([2.69]) -AC_INIT([pdns], [4.9.14]) +AC_INIT([pdns], [4.9.15]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip tar-ustar -Wno-portability subdir-objects parallel-tests 1.11]) AM_SILENT_RULES([yes]) diff -Nru pdns-4.9.14/debian/changelog pdns-4.9.15/debian/changelog --- pdns-4.9.14/debian/changelog 2026-04-26 19:40:16.000000000 +0000 +++ pdns-4.9.15/debian/changelog 2026-05-17 21:31:56.000000000 +0000 @@ -1,3 +1,10 @@ +pdns (4.9.15-0+deb13u1) trixie-security; urgency=medium + + * New upstream version 4.9.15, fixing security issues + CVE-2026-42000, CVE-2026-42001, CVE-2026-42002, CVE-2026-42396. + + -- Chris Hofstaedtler Sun, 17 May 2026 23:31:56 +0200 + pdns (4.9.14-0+deb13u1) trixie-security; urgency=medium * New upstream version 4.9.14, fixing security issues CVE-2026-33257, diff -Nru pdns-4.9.14/docs/calidns.1 pdns-4.9.15/docs/calidns.1 --- pdns-4.9.14/docs/calidns.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/calidns.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CALIDNS" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "CALIDNS" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME calidns \- A DNS recursor testing tool .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnsbulktest.1 pdns-4.9.15/docs/dnsbulktest.1 --- pdns-4.9.14/docs/dnsbulktest.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/dnsbulktest.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSBULKTEST" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSBULKTEST" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsbulktest \- A debugging tool for intermittent resolver failures .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnsgram.1 pdns-4.9.15/docs/dnsgram.1 --- pdns-4.9.14/docs/dnsgram.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/dnsgram.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSGRAM" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSGRAM" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsgram \- A debugging tool for intermittent resolver failures .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnspcap2calidns.1 pdns-4.9.15/docs/dnspcap2calidns.1 --- pdns-4.9.14/docs/dnspcap2calidns.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/dnspcap2calidns.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSPCAP2CALIDNS" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSPCAP2CALIDNS" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnspcap2calidns \- A tool to convert PCAPs of DNS traffic to calidns input .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnspcap2protobuf.1 pdns-4.9.15/docs/dnspcap2protobuf.1 --- pdns-4.9.14/docs/dnspcap2protobuf.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/dnspcap2protobuf.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSPCAP2PROTOBUF" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSPCAP2PROTOBUF" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnspcap2protobuf \- A tool to convert PCAPs of DNS traffic to PowerDNS Protobuf .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnsreplay.1 pdns-4.9.15/docs/dnsreplay.1 --- pdns-4.9.14/docs/dnsreplay.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/dnsreplay.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSREPLAY" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSREPLAY" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsreplay \- A PowerDNS nameserver debugging tool .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnsscan.1 pdns-4.9.15/docs/dnsscan.1 --- pdns-4.9.14/docs/dnsscan.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/dnsscan.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSSCAN" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSSCAN" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsscan \- List the amount of queries per qtype in a pcap .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnsscope.1 pdns-4.9.15/docs/dnsscope.1 --- pdns-4.9.14/docs/dnsscope.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/dnsscope.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSSCOPE" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSSCOPE" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsscope \- A PowerDNS nameserver debugging tool .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnstcpbench.1 pdns-4.9.15/docs/dnstcpbench.1 --- pdns-4.9.14/docs/dnstcpbench.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/dnstcpbench.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSTCPBENCH" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSTCPBENCH" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnstcpbench \- tool to perform TCP benchmarking of nameservers .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dnswasher.1 pdns-4.9.15/docs/dnswasher.1 --- pdns-4.9.14/docs/dnswasher.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/dnswasher.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSWASHER" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSWASHER" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnswasher \- A PowerDNS nameserver debugging tool .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/dumresp.1 pdns-4.9.15/docs/dumresp.1 --- pdns-4.9.14/docs/dumresp.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/dumresp.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DUMRESP" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "DUMRESP" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME dumresp \- A dumb DNS responder .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/ixfrdist.1 pdns-4.9.15/docs/ixfrdist.1 --- pdns-4.9.14/docs/ixfrdist.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/ixfrdist.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "IXFRDIST" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "IXFRDIST" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME ixfrdist \- An IXFR/AXFR-only server that re-distributes zones .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/ixfrdist.yml.5 pdns-4.9.15/docs/ixfrdist.yml.5 --- pdns-4.9.14/docs/ixfrdist.yml.5 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/ixfrdist.yml.5 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "IXFRDIST.YML" "5" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "IXFRDIST.YML" "5" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME ixfrdist.yml \- The ixfrdist configuration file .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/ixplore.1 pdns-4.9.15/docs/ixplore.1 --- pdns-4.9.14/docs/ixplore.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/ixplore.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "IXPLORE" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "IXPLORE" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME ixplore \- A tool that provides insights into IXFRs .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/nproxy.1 pdns-4.9.15/docs/nproxy.1 --- pdns-4.9.14/docs/nproxy.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/nproxy.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "NPROXY" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "NPROXY" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME nproxy \- DNS notification proxy .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/nsec3dig.1 pdns-4.9.15/docs/nsec3dig.1 --- pdns-4.9.14/docs/nsec3dig.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/nsec3dig.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "NSEC3DIG" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "NSEC3DIG" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME nsec3dig \- Show and validate NSEC3 proofs .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/pdns_control.1 pdns-4.9.15/docs/pdns_control.1 --- pdns-4.9.14/docs/pdns_control.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/pdns_control.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNS_CONTROL" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNS_CONTROL" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdns_control \- Control the PowerDNS nameserver .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/pdns_notify.1 pdns-4.9.15/docs/pdns_notify.1 --- pdns-4.9.14/docs/pdns_notify.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/pdns_notify.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNS_NOTIFY" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNS_NOTIFY" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdns_notify \- A simple DNS NOTIFY sender .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/pdns_server.1 pdns-4.9.15/docs/pdns_server.1 --- pdns-4.9.14/docs/pdns_server.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/pdns_server.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNS_SERVER" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNS_SERVER" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdns_server \- The PowerDNS Authoritative Nameserver .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/pdnsutil.1 pdns-4.9.15/docs/pdnsutil.1 --- pdns-4.9.14/docs/pdnsutil.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/pdnsutil.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNSUTIL" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNSUTIL" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdnsutil \- PowerDNS record and DNSSEC command and control .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/saxfr.1 pdns-4.9.15/docs/saxfr.1 --- pdns-4.9.14/docs/saxfr.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/saxfr.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SAXFR" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "SAXFR" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME saxfr \- Perform AXFRs and show information about it .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/sdig.1 pdns-4.9.15/docs/sdig.1 --- pdns-4.9.14/docs/sdig.1 2026-04-08 09:59:47.000000000 +0000 +++ pdns-4.9.15/docs/sdig.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SDIG" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "SDIG" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME sdig \- Perform a DNS query and show the results .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/zone2json.1 pdns-4.9.15/docs/zone2json.1 --- pdns-4.9.14/docs/zone2json.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/zone2json.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ZONE2JSON" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "ZONE2JSON" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME zone2json \- convert BIND zones to JSON .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/zone2ldap.1 pdns-4.9.15/docs/zone2ldap.1 --- pdns-4.9.14/docs/zone2ldap.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/zone2ldap.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ZONE2LDAP" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "ZONE2LDAP" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME zone2ldap \- convert zonefiles to ldif .SH SYNOPSIS diff -Nru pdns-4.9.14/docs/zone2sql.1 pdns-4.9.15/docs/zone2sql.1 --- pdns-4.9.14/docs/zone2sql.1 2026-04-08 09:59:48.000000000 +0000 +++ pdns-4.9.15/docs/zone2sql.1 2026-05-11 09:10:38.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ZONE2SQL" "1" "Apr 08, 2026" "" "PowerDNS Authoritative Server" +.TH "ZONE2SQL" "1" "May 11, 2026" "" "PowerDNS Authoritative Server" .SH NAME zone2sql \- convert BIND zones to SQL .SH SYNOPSIS diff -Nru pdns-4.9.14/ext/yahttp/yahttp/reqresp.cpp pdns-4.9.15/ext/yahttp/yahttp/reqresp.cpp --- pdns-4.9.14/ext/yahttp/yahttp/reqresp.cpp 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/ext/yahttp/yahttp/reqresp.cpp 2026-05-11 09:08:45.000000000 +0000 @@ -201,20 +201,25 @@ if (buffer.size() < chunk_size+2 || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return crlf=2; } else if (buffer.at(chunk_size) != '\n') return false; - if (bodybuf.str().length() + chunk_size > maxbody) { + if (bodysize + chunk_size > maxbody) { throw ParseError("Chunked body is too large"); } std::string tmp = buffer.substr(0, chunk_size); buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf); bodybuf << tmp; + bodysize += chunk_size; chunk_size = 0; if (buffer.size() == 0) break; // just in case } } else { - if (bodybuf.str().length() + buffer.length() > maxbody) + if (bodysize + buffer.length() > maxbody) { bodybuf << buffer.substr(0, maxbody - bodybuf.str().length()); - else + bodysize = maxbody; + } + else { bodybuf << buffer; + bodysize += buffer.length(); + } buffer = ""; } } diff -Nru pdns-4.9.14/ext/yahttp/yahttp/reqresp.hpp pdns-4.9.15/ext/yahttp/yahttp/reqresp.hpp --- pdns-4.9.14/ext/yahttp/yahttp/reqresp.hpp 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/ext/yahttp/yahttp/reqresp.hpp 2026-05-11 09:08:45.000000000 +0000 @@ -312,7 +312,8 @@ std::ostringstream bodybuf; //target->initialize(); }; // 1 && - (!hasBody || - (bodybuf.str().size() <= maxbody && - bodybuf.str().size() >= minbody) - ) - ); + (!hasBody || (bodysize <= maxbody && bodysize >= minbody))); }; //headers.find("content-type"); if (cpos != target->headers.end() && Utility::iequals(cpos->second, "application/x-www-form-urlencoded", 32)) { - target->postvars = Utility::parseUrlParameters(bodybuf.str()); + target->postvars = Utility::parseUrlParameters(body); } - target->body = bodybuf.str(); + target->body = std::move(body); } bodybuf.str(""); + bodysize = 0; this->target = NULL; }; // ebuf{}; + + for (char letter : name) { + switch (letter) { + case '$': + case '@': + case '"': + case ';': + case '(': + case ')': + snprintf(ebuf.data(), ebuf.size(), "\\%03u", static_cast(letter)); + ret += ebuf.data(); + break; + default: + ret += letter; + break; + } + } + return ret; +} + bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */) { if (d_transaction_id < 1) { @@ -283,7 +312,7 @@ string qname; if (d_transaction_qname.empty()) { - qname = rr.qname.toString(); + qname = bindEscape(rr.qname.toString()); } else if (rr.qname.isPartOf(d_transaction_qname)) { if (rr.qname == d_transaction_qname) { @@ -291,7 +320,7 @@ } else { DNSName relName = rr.qname.makeRelative(d_transaction_qname); - qname = relName.toStringNoDot(); + qname = bindEscape(relName.toStringNoDot()); } } else { @@ -308,8 +337,8 @@ case QType::CNAME: case QType::DNAME: case QType::NS: - stripDomainSuffix(&content, d_transaction_qname.toString()); - // fallthrough + stripDomainSuffix(&content, d_transaction_qname); + [[fallthrough]]; default: if (d_of && *d_of) { *d_of << qname << "\t" << rr.ttl << "\t" << rr.qtype.toString() << "\t" << content << endl; diff -Nru pdns-4.9.14/pdns/auth-catalogzone.cc pdns-4.9.15/pdns/auth-catalogzone.cc --- pdns-4.9.14/pdns/auth-catalogzone.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/auth-catalogzone.cc 2026-05-11 09:08:45.000000000 +0000 @@ -123,6 +123,26 @@ return dzr; } +static string txtEscape(const string &name) +{ + string ret; + char ebuf[5]; + + for(char i : name) { + if((unsigned char) i >= 127 || (unsigned char) i < 32) { + snprintf(ebuf, sizeof(ebuf), "\\%03u", (unsigned char)i); + ret += ebuf; + } + else if(i=='"' || i=='\\'){ + ret += '\\'; + ret += i; + } + else + ret += i; + } + return ret; +} + void CatalogInfo::toDNSZoneRecords(const DNSName& zone, vector& dzrs) const { DNSName prefix; @@ -138,7 +158,7 @@ dzr.dr.d_name = prefix; dzr.dr.d_ttl = 0; dzr.dr.d_type = QType::PTR; - dzr.dr.setContent(std::make_shared(d_zone.toString())); + dzr.dr.setContent(std::make_shared(d_zone)); dzrs.emplace_back(dzr); if (!d_coo.empty()) { @@ -153,7 +173,7 @@ dzr.dr.d_name = DNSName("group") + prefix; dzr.dr.d_ttl = 0; dzr.dr.d_type = QType::TXT; - dzr.dr.setContent(std::make_shared("\"" + group + "\"")); + dzr.dr.setContent(std::make_shared("\"" + txtEscape(group) + "\"")); dzrs.emplace_back(dzr); } } diff -Nru pdns-4.9.14/pdns/auth-main.cc pdns-4.9.15/pdns/auth-main.cc --- pdns-4.9.14/pdns/auth-main.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/auth-main.cc 2026-05-11 09:08:45.000000000 +0000 @@ -336,6 +336,7 @@ #ifdef ENABLE_GSS_TSIG ::arg().setSwitch("enable-gss-tsig", "Enable GSS TSIG processing") = "no"; + ::arg().set("gss-max-contexts", "The maximum number of simultaneous GSS contexts allowed") = "1000"; #endif ::arg().setDefaults(); } @@ -712,6 +713,9 @@ #endif #ifdef ENABLE_GSS_TSIG g_doGssTSIG = ::arg().mustDo("enable-gss-tsig"); + if (g_doGssTSIG) { + GssContext::s_maxGssContexts = ::arg().asNum("gss-max-contexts"); + } #endif DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold")); diff -Nru pdns-4.9.14/pdns/auth-secondarycommunicator.cc pdns-4.9.15/pdns/auth-secondarycommunicator.cc --- pdns-4.9.14/pdns/auth-secondarycommunicator.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/auth-secondarycommunicator.cc 2026-05-11 09:08:45.000000000 +0000 @@ -1154,7 +1154,7 @@ TSIGRecordContent trc; DNSName tsigkeyname; dp.getTSIGDetails(&trc, &tsigkeyname); - P->tryAutoPrimarySynchronous(dp, tsigkeyname); // FIXME could use some error logging + P->tryAutoPrimarySynchronous(dp, tsigkeyname); } if (rdomains.empty()) { // if we have priority domains, check them first B->getUnfreshSecondaryInfos(&rdomains); diff -Nru pdns-4.9.14/pdns/axfr-retriever.cc pdns-4.9.15/pdns/axfr-retriever.cc --- pdns-4.9.14/pdns/axfr-retriever.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/axfr-retriever.cc 2026-05-11 09:08:45.000000000 +0000 @@ -125,46 +125,44 @@ d_receivedBytes += (uint16_t) len; - MOADNSParser mdp(false, d_buf.data(), len); - - int err = mdp.d_header.rcode; + try { + MOADNSParser mdp(false, d_buf.data(), len); - if(err) { - throw ResolverException("AXFR chunk error: " + RCode::to_s(err)); - } + int err = mdp.d_header.rcode; + if (err != 0) { + throw ResolverException("AXFR chunk error: " + RCode::to_s(err)); + } - if(mdp.d_header.tc) { - throw ResolverException("AXFR chunk had TC bit set"); - } + if(mdp.d_header.tc) { + throw ResolverException("AXFR chunk had TC bit set"); + } - try { d_tsigVerifier.check(std::string(d_buf.data(), len), mdp); - } - catch(const std::runtime_error& re) { - throw ResolverException(re.what()); - } - if(!records) { - err = parseResult(mdp, DNSName(), 0, 0, &res); + if (records == nullptr) { + err = parseResult(mdp, DNSName(), 0, 0, &res); + if (err == 0) { + for(const auto& answer : mdp.d_answers) + if (answer.first.d_type == QType::SOA) + d_soacount++; + } + } + else { + records->clear(); + records->reserve(mdp.d_answers.size()); - if (!err) { - for(const auto& answer : mdp.d_answers) - if (answer.first.d_type == QType::SOA) + for(auto& r: mdp.d_answers) { + if (r.first.d_type == QType::SOA) { d_soacount++; - } - } - else { - records->clear(); - records->reserve(mdp.d_answers.size()); - - for(auto& r: mdp.d_answers) { - if (r.first.d_type == QType::SOA) { - d_soacount++; - } + } - records->push_back(std::move(r.first)); + records->push_back(std::move(r.first)); + } } } + catch(const std::runtime_error& re) { + throw ResolverException(re.what()); + } return true; } diff -Nru pdns-4.9.14/pdns/comfun.cc pdns-4.9.15/pdns/comfun.cc --- pdns-4.9.14/pdns/comfun.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/comfun.cc 2026-05-11 09:08:45.000000000 +0000 @@ -550,6 +550,9 @@ } // cout<topAllocatorsString(20)<(answer.first); - if (!content) { - g_log<(answer.first); + if (!content) { + g_log<(answer.first); - if (!content) { - g_log<(answer.first); + if (!content) { + g_log< void GenericDNSPacketWriter::xfr48BitInt(uint64_t val) { + if ((val >> 48) != 0) { + throw runtime_error("Value too large to fit in 48 bits"); + } std::array bytes; uint16_t theLeft = htons((val >> 32)&0xffffU); uint32_t theRight = htonl(val & 0xffffffffU); @@ -145,23 +148,32 @@ d_content.insert(d_content.end(), val.content, val.content + sizeof(val.content)); } -template void GenericDNSPacketWriter::xfr32BitInt(uint32_t val) +template void GenericDNSPacketWriter::xfr32BitInt(uint64_t val) { - uint32_t rval=htonl(val); + if (val > std::numeric_limits::max()) { + throw runtime_error("Value too large to fit in 32 bits"); + } + uint32_t rval=htonl(static_cast(val)); uint8_t* ptr=reinterpret_cast(&rval); d_content.insert(d_content.end(), ptr, ptr+4); } -template void GenericDNSPacketWriter::xfr16BitInt(uint16_t val) +template void GenericDNSPacketWriter::xfr16BitInt(uint64_t val) { - uint16_t rval=htons(val); + if (val > std::numeric_limits::max()) { + throw runtime_error("Value too large to fit in 16 bits"); + } + uint16_t rval=htons(static_cast(val)); uint8_t* ptr=reinterpret_cast(&rval); d_content.insert(d_content.end(), ptr, ptr+2); } -template void GenericDNSPacketWriter::xfr8BitInt(uint8_t val) +template void GenericDNSPacketWriter::xfr8BitInt(uint64_t val) { - d_content.push_back(val); + if (val > std::numeric_limits::max()) { + throw runtime_error("Value too large to fit in 8 bits"); + } + d_content.push_back(static_cast(val)); } diff -Nru pdns-4.9.14/pdns/dnswriter.hh pdns-4.9.15/pdns/dnswriter.hh --- pdns-4.9.14/pdns/dnswriter.hh 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/dnswriter.hh 2026-05-11 09:08:45.000000000 +0000 @@ -87,8 +87,8 @@ void xfr48BitInt(uint64_t val); void xfrNodeOrLocatorID(const NodeOrLocatorID& val); - void xfr32BitInt(uint32_t val); - void xfr16BitInt(uint16_t val); + void xfr32BitInt(uint64_t val); + void xfr16BitInt(uint64_t val); void xfrType(uint16_t val) { xfr16BitInt(val); @@ -125,7 +125,7 @@ xfr32BitInt(val); } - void xfr8BitInt(uint8_t val); + void xfr8BitInt(uint64_t val); void xfrName(const DNSName& label, bool compress=false, bool noDot=false); void xfrText(const string& text, bool multi=false, bool lenField=true); diff -Nru pdns-4.9.14/pdns/dynlistener.cc pdns-4.9.15/pdns/dynlistener.cc --- pdns-4.9.14/pdns/dynlistener.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/dynlistener.cc 2026-05-11 09:08:45.000000000 +0000 @@ -242,7 +242,7 @@ } string password(mesg.data()); boost::trim(password); - if(password.empty() || password!=arg()["tcp-control-secret"]) { + if(password.empty() || !constantTimeStringEquals(password, arg()["tcp-control-secret"])) { g_log<>> s_gss_accept_creds; static LockGuarded>> s_gss_init_creds; -class GssSecContext : boost::noncopyable +class GssSecContext { public: GssSecContext(std::shared_ptr cred) @@ -172,7 +174,7 @@ } d_state{GssStateInitial}; }; // GssSecContext -static LockGuarded>> s_gss_sec_context; +static LockGuarded>>> s_gss_sec_context; template static void doExpire(T& m, time_t now) @@ -188,9 +190,29 @@ } } +// Same as above, for s_gss_sec_context +template +static void doExpireL(T& m, time_t now) +{ + auto lock = m.lock(); + for (auto i = lock->begin(); i != lock->end();) { + time_t expiretime{0}; + { + auto ctx = i->second->lock(); + expiretime = ctx->d_expires; + } + if (now > expiretime) { + i = lock->erase(i); + } + else { + ++i; + } + } +} + static void expire() { - static time_t s_last_expired; + static std::atomic s_last_expired; time_t now = time(nullptr); if (now - s_last_expired < TSIG_GSS_EXPIRE_INTERVAL) { return; @@ -198,7 +220,7 @@ s_last_expired = now; doExpire(s_gss_init_creds, now); doExpire(s_gss_accept_creds, now); - doExpire(s_gss_sec_context, now); + doExpireL(s_gss_sec_context, now); } bool GssContext::supported() { return true; } @@ -237,18 +259,56 @@ auto it = lock->find(d_label); if (it != lock->end()) { d_secctx = it->second; - d_type = d_secctx->d_type; + auto ctx = d_secctx->lock(); + d_type = ctx->d_type; } } bool GssContext::expired() { - return (!d_secctx || (d_secctx->d_expires > -1 && d_secctx->d_expires < time(nullptr))); + if (!d_secctx) { + return true; + } + auto ctx = d_secctx->lock(); + return (ctx->d_expires > -1 && ctx->d_expires < time(nullptr)); } bool GssContext::valid() { - return (d_secctx && !expired() && d_secctx->d_state == GssSecContext::GssStateComplete); + if (expired()) { + return false; + } + auto ctx = d_secctx->lock(); + return ctx->d_state == GssSecContext::GssStateComplete; +} + +bool GssContext::createOrReuseContext(std::shared_ptr cred) +{ + // see if we can find a context in non-completed state + if (d_secctx) { + auto ctx = d_secctx->lock(); + if (ctx->d_state != GssSecContext::GssStateNegotiate) { + d_error = GSS_CONTEXT_INVALID; + return false; + } + } + else { + // make context + auto lock = s_gss_sec_context.lock(); + if (lock->size() == s_maxGssContexts) { + d_error = GSS_CONTEXT_LIMIT_REACHED; + d_gss_errors.push_back("Limit of concurrent GSS contexts reached"); + return false; + } + d_secctx = std::make_shared>(cred); + { + auto ctx = d_secctx->lock(); + ctx->d_state = GssSecContext::GssStateNegotiate; + ctx->d_type = d_type; + } + (*lock)[d_label] = d_secctx; + } + return true; } bool GssContext::init(const std::string& input, std::string& output) @@ -277,53 +337,45 @@ cred = it->second; } - // see if we can find a context in non-completed state - if (d_secctx) { - if (d_secctx->d_state != GssSecContext::GssStateNegotiate) { - d_error = GSS_CONTEXT_INVALID; - return false; - } - } - else { - // make context - auto lock = s_gss_sec_context.lock(); - d_secctx = std::make_shared(cred); - d_secctx->d_state = GssSecContext::GssStateNegotiate; - d_secctx->d_type = d_type; - (*lock)[d_label] = d_secctx; + if (!createOrReuseContext(cred)) { + return false; } recv_tok.length = input.size(); recv_tok.value = const_cast(static_cast(input.c_str())); - if (!d_peerPrincipal.empty()) { - buffer.value = const_cast(static_cast(d_peerPrincipal.c_str())); - buffer.length = d_peerPrincipal.size(); - maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_secctx->d_peer_name)); - if (maj != GSS_S_COMPLETE) { - processError("gss_import_name", maj, min); - return false; + { + auto ctx = d_secctx->lock(); + + if (!d_peerPrincipal.empty()) { + buffer.value = const_cast(static_cast(d_peerPrincipal.c_str())); + buffer.length = d_peerPrincipal.size(); + maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(ctx->d_peer_name)); + if (maj != GSS_S_COMPLETE) { + processError("gss_import_name", maj, min); + return false; + } } - } - maj = gss_init_sec_context(&min, cred->d_cred, &d_secctx->d_ctx, d_secctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, nullptr, &send_tok, &flags, &expires); + maj = gss_init_sec_context(&min, cred->d_cred, &ctx->d_ctx, ctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, nullptr, &send_tok, &flags, &expires); - if (send_tok.length > 0) { - output.assign(static_cast(send_tok.value), send_tok.length); - tmp_maj = gss_release_buffer(&tmp_min, &send_tok); - } + if (send_tok.length > 0) { + output.assign(static_cast(send_tok.value), send_tok.length); + tmp_maj = gss_release_buffer(&tmp_min, &send_tok); + } - if (maj == GSS_S_COMPLETE) { - // We do not want forever - if (expires == GSS_C_INDEFINITE) { - expires = 60; + if (maj == GSS_S_COMPLETE) { + // We do not want forever + if (expires == GSS_C_INDEFINITE) { + expires = 60; + } + ctx->d_expires = time(nullptr) + expires; + ctx->d_state = GssSecContext::GssStateComplete; + return true; + } + else if (maj != GSS_S_CONTINUE_NEEDED) { + processError("gss_init_sec_context", maj, min); } - d_secctx->d_expires = time(nullptr) + expires; - d_secctx->d_state = GssSecContext::GssStateComplete; - return true; - } - else if (maj != GSS_S_CONTINUE_NEEDED) { - processError("gss_init_sec_context", maj, min); } return (maj == GSS_S_CONTINUE_NEEDED); @@ -355,44 +407,36 @@ cred = it->second; } - // see if we can find a context in non-completed state - if (d_secctx) { - if (d_secctx->d_state != GssSecContext::GssStateNegotiate) { - d_error = GSS_CONTEXT_INVALID; - return false; - } - } - else { - // make context - auto lock = s_gss_sec_context.lock(); - d_secctx = std::make_shared(cred); - d_secctx->d_state = GssSecContext::GssStateNegotiate; - d_secctx->d_type = d_type; - (*lock)[d_label] = d_secctx; + if (!createOrReuseContext(cred)) { + return false; } recv_tok.length = input.size(); recv_tok.value = const_cast(static_cast(input.c_str())); - maj = gss_accept_sec_context(&min, &d_secctx->d_ctx, cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &d_secctx->d_peer_name, nullptr, &send_tok, &flags, &expires, nullptr); + { + auto ctx = d_secctx->lock(); + maj = gss_accept_sec_context(&min, &ctx->d_ctx, cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &ctx->d_peer_name, nullptr, &send_tok, &flags, &expires, nullptr); - if (send_tok.length > 0) { - output.assign(static_cast(send_tok.value), send_tok.length); - tmp_maj = gss_release_buffer(&tmp_min, &send_tok); - } + if (send_tok.length > 0) { + output.assign(static_cast(send_tok.value), send_tok.length); + tmp_maj = gss_release_buffer(&tmp_min, &send_tok); + } - if (maj == GSS_S_COMPLETE) { - // We do not want forever - if (expires == GSS_C_INDEFINITE) { - expires = 60; + if (maj == GSS_S_COMPLETE) { + // We do not want forever + if (expires == GSS_C_INDEFINITE) { + expires = 60; + } + ctx->d_expires = time(nullptr) + expires; + ctx->d_state = GssSecContext::GssStateComplete; + return true; + } + else if (maj != GSS_S_CONTINUE_NEEDED) { + processError("gss_accept_sec_context", maj, min); } - d_secctx->d_expires = time(nullptr) + expires; - d_secctx->d_state = GssSecContext::GssStateComplete; - return true; - } - else if (maj != GSS_S_CONTINUE_NEEDED) { - processError("gss_accept_sec_context", maj, min); } + return (maj == GSS_S_CONTINUE_NEEDED); }; @@ -407,7 +451,10 @@ recv_tok.length = input.size(); recv_tok.value = const_cast(static_cast(input.c_str())); - maj = gss_get_mic(&min, d_secctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok); + { + auto ctx = d_secctx->lock(); + maj = gss_get_mic(&min, ctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok); + } if (send_tok.length > 0) { output.assign(static_cast(send_tok.value), send_tok.length); @@ -433,7 +480,10 @@ sign_tok.length = signature.size(); sign_tok.value = const_cast(static_cast(signature.c_str())); - maj = gss_verify_mic(&min, d_secctx->d_ctx, &recv_tok, &sign_tok, nullptr); + { + auto ctx = d_secctx->lock(); + maj = gss_verify_mic(&min, ctx->d_ctx, &recv_tok, &sign_tok, nullptr); + } if (maj != GSS_S_COMPLETE) { processError("gss_get_mic", maj, min); @@ -472,20 +522,23 @@ gss_buffer_desc value; OM_uint32 maj, min; - if (d_secctx->d_peer_name != GSS_C_NO_NAME) { - maj = gss_display_name(&min, d_secctx->d_peer_name, &value, nullptr); - if (maj == GSS_S_COMPLETE && value.length > 0) { - name.assign(static_cast(value.value), value.length); - maj = gss_release_buffer(&min, &value); - return true; + { + auto ctx = d_secctx->lock(); + if (ctx->d_peer_name != GSS_C_NO_NAME) { + maj = gss_display_name(&min, ctx->d_peer_name, &value, nullptr); + if (maj == GSS_S_COMPLETE && value.length > 0) { + name.assign(static_cast(value.value), value.length); + maj = gss_release_buffer(&min, &value); + return true; + } + else { + return false; + } } else { return false; } } - else { - return false; - } } std::tuple GssContext::getCounts() diff -Nru pdns-4.9.14/pdns/gss_context.hh pdns-4.9.15/pdns/gss_context.hh --- pdns-4.9.14/pdns/gss_context.hh 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/gss_context.hh 2026-05-11 09:08:45.000000000 +0000 @@ -28,6 +28,7 @@ #include "namespaces.hh" #include "pdnsexception.hh" #include "dns.hh" +#include "lock.hh" #ifdef ENABLE_GSS_TSIG #include @@ -44,7 +45,8 @@ GSS_CONTEXT_NOT_INITIALIZED, GSS_CONTEXT_INVALID, GSS_CONTEXT_EXPIRED, - GSS_CONTEXT_ALREADY_INITIALIZED + GSS_CONTEXT_ALREADY_INITIALIZED, + GSS_CONTEXT_LIMIT_REACHED, }; //! GSS context types @@ -56,6 +58,7 @@ }; class GssSecContext; +class GssCredential; /*! Class for representing GSS names, such as host/host.domain.com@REALM. */ @@ -193,11 +196,15 @@ GssContextError getError(); // getErrorStrings() { return d_gss_errors; } // cred); #endif DNSName d_label; // d_gss_errors; // d_secctx; //> d_secctx; //qname.makeRelative(s_lua_record_ctx->zone)}; @@ -945,13 +947,13 @@ // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4) string ret; for (size_t index=4; index > 0; index--) { - auto octet = ip_parts.at(ip_parts.size() - index); - auto octetVal = std::stol(octet); // may throw - if (octetVal >= 0 && octetVal <= 255) { - ret += octet + "."; - } else { + const auto octet = ip_parts.at(ip_parts.size() - index); + size_t octetLength{0}; + auto octetVal = pdns::checked_stoi(octet, &octetLength); // may throw + if (octetLength != octet.length()) { // trailing chars after number return allZerosIP; } + ret += std::to_string(octetVal) + "."; } ret.resize(ret.size() - 1); // remove trailing dot after last octet return ret; @@ -971,13 +973,12 @@ return allZerosIP; } catch (const PDNSException &e) { return allZerosIP; - } catch (const std::exception &) { // thrown by std::stol + } catch (const std::exception &) { // thrown by pdns::checked_stoi return allZerosIP; } }); lua.writeFunction("createForward6", []() { - static string allZerosIP{"::"}; try { DNSName rel{s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone)}; @@ -1015,9 +1016,9 @@ return address.toString(); } } - return allZerosIP; + return allZerosIP6; } catch (const PDNSException &e) { - return allZerosIP; + return allZerosIP6; } }); lua.writeFunction("createReverse6", [](const string &format, boost::optional> excp){ @@ -1081,7 +1082,7 @@ lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional fallback) -> vector { ComboAddress ca(address); - if (nmg.match(ComboAddress(address))) { + if (nmg.match(ca)) { return {address}; } else { if (fallback) { @@ -1093,9 +1094,9 @@ } if (ca.isIPv4()) { - return {string("0.0.0.0")}; + return {allZerosIP}; } else { - return {string("::")}; + return {allZerosIP6}; } } }); diff -Nru pdns-4.9.14/pdns/misc.cc pdns-4.9.15/pdns/misc.cc --- pdns-4.9.14/pdns/misc.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/misc.cc 2026-05-11 09:08:45.000000000 +0000 @@ -305,42 +305,31 @@ return {buffer.data()}; } -static bool ciEqual(const string& lhs, const string& rhs) -{ - if (lhs.size() != rhs.size()) { - return false; - } - - string::size_type pos = 0; - const string::size_type epos = lhs.size(); - for (; pos < epos; ++pos) { - if (dns_tolower(lhs[pos]) != dns_tolower(rhs[pos])) { - return false; - } - } - return true; -} - /** does domain end on suffix? Is smart about "wwwds9a.nl" "ds9a.nl" not matching */ -static bool endsOn(const string &domain, const string &suffix) +static bool endsOn(const string& domain, const string& suffix) { - if( suffix.empty() || ciEqual(domain, suffix) ) { - return true; - } - - if(domain.size() <= suffix.size()) { + if (domain.size() <= suffix.size()) { return false; } string::size_type dpos = domain.size() - suffix.size() - 1; - string::size_type spos = 0; - if (domain[dpos++] != '.') { return false; } + // That dot might have been escaped. So we now need to count how many '\' + // characters we can find in a row before it; if their number is odd, the + // dot is escaped and we are not a proper suffix. + size_t slashes{0}; + while (dpos >= 2 + slashes && domain.at(dpos - 2 - slashes) == '\\') { + ++slashes; + } + if ((slashes % 2) != 0) { + return false; + } - for(; dpos < domain.size(); ++dpos, ++spos) { - if (dns_tolower(domain[dpos]) != dns_tolower(suffix[spos])) { + string::size_type spos = 0; + for (; dpos < domain.size(); ++dpos, ++spos) { + if (!pdns_iequals_ch(domain[dpos], suffix[spos])) { return false; } } @@ -348,24 +337,22 @@ return true; } -/** strips a domain suffix from a domain, returns true if it stripped */ -bool stripDomainSuffix(string *qname, const string &domain) +/** strips a domain suffix from a domain */ +void stripDomainSuffix(string *qname, const DNSName &zonename) { - if (!endsOn(*qname, domain)) { - return false; - } + std::string domain = zonename.toString(); - if (toLower(*qname) == toLower(domain)) { - *qname="@"; + if (domain.empty()) { + return; } - else { - if ((*qname)[qname->size() - domain.size() - 1] != '.') { - return false; - } - - qname->resize(qname->size() - domain.size()-1); + if (pdns_iequals(*qname, domain)) { + *qname = "@"; + return; + } + if (endsOn(*qname, domain)) { + auto prefix = qname->size() - domain.size(); + qname->resize(prefix - 1); // also strip dot } - return true; } // returns -1 in case if error, 0 if no data is available, 1 if there is. In the first two cases, errno is set diff -Nru pdns-4.9.14/pdns/misc.hh pdns-4.9.15/pdns/misc.hh --- pdns-4.9.14/pdns/misc.hh 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/misc.hh 2026-05-11 09:08:45.000000000 +0000 @@ -86,7 +86,7 @@ string nowTime(); string unquotify(const string &item); string humanDuration(time_t passed); -bool stripDomainSuffix(string *qname, const string &domain); +void stripDomainSuffix(string *qname, const DNSName &zonename); void stripLine(string &line); std::optional getHostname(); std::string getCarbonHostName(); diff -Nru pdns-4.9.14/pdns/packethandler.cc pdns-4.9.15/pdns/packethandler.cc --- pdns-4.9.14/pdns/packethandler.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/packethandler.cc 2026-05-11 09:08:45.000000000 +0000 @@ -80,21 +80,6 @@ d_pdl = std::make_unique(); d_pdl->loadFile(fname); // XXX exception handling? } - fname = ::arg()["lua-dnsupdate-policy-script"]; - if (fname.empty()) - { - d_update_policy_lua = nullptr; - } - else - { - d_update_policy_lua = std::make_unique(); - try { - d_update_policy_lua->loadFile(fname); - } - catch (const std::runtime_error&) { - d_update_policy_lua = nullptr; - } - } } UeberBackend *PacketHandler::getBackend() @@ -988,20 +973,17 @@ */ -int PacketHandler::tryAutoPrimary(const DNSPacket& p, const DNSName& tsigkeyname) +int PacketHandler::tryAutoPrimary(const DNSPacket& p) // NOLINT(readability-identifier-length) + { - if(p.d_tcp) - { - // do it right now if the client is TCP - // rarely happens - return tryAutoPrimarySynchronous(p, tsigkeyname); - } - else - { - // queue it if the client is on UDP - Communicator.addTryAutoPrimaryRequest(p); - return 0; - } + if(p.d_tcp) { + // Do it right now if the client is TCP (rarely happens) + return tryAutoPrimarySynchronous(p, p.getTSIGKeyname()); + } + // Queue it if the client is on UDP; the communicator will invoke + // tryAutoPrimarySynchronous later. + Communicator.addTryAutoPrimaryRequest(p); + return RCode::NoError; } int PacketHandler::tryAutoPrimarySynchronous(const DNSPacket& p, const DNSName& tsigkeyname) @@ -1068,6 +1050,9 @@ meta.push_back(tsigkeyname.toStringNoDot()); db->setDomainMetadata(p.qdomain, "AXFR-MASTER-TSIG", meta); } + // Now that we have created the secondary, fetch its contents. + di.receivedNotify = true; + Communicator.addSecondaryCheckRequest(di, p.getInnerRemote()); } catch(PDNSException& ae) { g_log << Logger::Error << "Database error trying to create " << p.qdomain << " for potential autoprimary " << remote << ": " << ae.reason << endl; @@ -1126,7 +1111,7 @@ if(!B.getDomainInfo(p.qdomain, di, false) || !di.backend) { if(::arg().mustDo("autosecondary")) { g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " for which we are not authoritative, trying autoprimary" << endl; - return tryAutoPrimary(p, p.getTSIGKeyname()); + return tryAutoPrimary(p); } g_log< s_deleteCDSContent; private: - int tryAutoPrimary(const DNSPacket& p, const DNSName& tsigkeyname); + int tryAutoPrimary(const DNSPacket& p); int processNotify(const DNSPacket& ); void addRootReferral(DNSPacket& r); int doChaosRequest(const DNSPacket& p, std::unique_ptr& r, DNSName &target) const; @@ -116,7 +116,6 @@ bool d_dnssec{false}; SOAData d_sd; std::unique_ptr d_pdl; - std::unique_ptr d_update_policy_lua; std::unique_ptr s_LUA; UeberBackend B; // every thread an own instance DNSSECKeeper d_dk; // B is shared with DNSSECKeeper diff -Nru pdns-4.9.14/pdns/resolver.cc pdns-4.9.15/pdns/resolver.cc --- pdns-4.9.14/pdns/resolver.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/resolver.cc 2026-05-11 09:08:45.000000000 +0000 @@ -267,42 +267,52 @@ throw ResolverException("recvfrom error waiting for answer: "+stringerror()); } - MOADNSParser mdp(false, (char*)buf, err); - *id=mdp.d_header.id; - *domain = mdp.d_qname; - - if(domain->empty()) - throw ResolverException("SOA query to '" + remote->toLogString() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); - - if(mdp.d_answers.empty()) - throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); - - if(mdp.d_qtype != QType::SOA) - throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type"); - - if(mdp.d_header.rcode != 0) - throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode)); - - *theirInception = *theirExpire = 0; - bool gotSOA=false; - for(const MOADNSParser::answers_t::value_type& drc : mdp.d_answers) { - if(drc.first.d_type == QType::SOA && drc.first.d_name == *domain) { - auto src = getRR(drc.first); - if (src) { - *theirSerial = src->d_st.serial; - gotSOA = true; - } + bool gotSOA{false}; + try { + MOADNSParser mdp(false, (char*)buf, err); + *id=mdp.d_header.id; + *domain = mdp.d_qname; + + if(domain->empty()) { + throw ResolverException("SOA query to '" + remote->toLogString() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); + } + + if(mdp.d_answers.empty()) { + throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); + } + + if(mdp.d_qtype != QType::SOA) { + throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type"); } - if(drc.first.d_type == QType::RRSIG && drc.first.d_name == *domain) { - auto rrc = getRR(drc.first); - if(rrc && rrc->d_type == QType::SOA) { - *theirInception= std::max(*theirInception, rrc->d_siginception); - *theirExpire = std::max(*theirExpire, rrc->d_sigexpire); + + if(mdp.d_header.rcode != 0) { + throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode)); + } + + *theirInception = *theirExpire = 0; + for(const MOADNSParser::answers_t::value_type& drc : mdp.d_answers) { + if(drc.first.d_type == QType::SOA && drc.first.d_name == *domain) { + auto src = getRR(drc.first); + if (src) { + *theirSerial = src->d_st.serial; + gotSOA = true; + } + } + if(drc.first.d_type == QType::RRSIG && drc.first.d_name == *domain) { + auto rrc = getRR(drc.first); + if(rrc && rrc->d_type == QType::SOA) { + *theirInception= std::max(*theirInception, rrc->d_siginception); + *theirExpire = std::max(*theirExpire, rrc->d_sigexpire); + } } } } - if(!gotSOA) + catch (const MOADNSException& exc) { + throw ResolverException("SOA Query to '" + remote->toLogString() + "' produced ill-formed response: " + exc.what()); + } + if(!gotSOA) { throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA"); + } return true; } @@ -337,6 +347,9 @@ catch(ResolverException &re) { throw ResolverException(re.reason+" from "+to.toLogString()); } + catch (const MOADNSException& exc) { + throw ResolverException(std::string(exc.what()) + " from " + to.toLogString()); + } } int Resolver::resolve(const ComboAddress& ipport, const DNSName &domain, int type, Resolver::res_t* res) { diff -Nru pdns-4.9.14/pdns/rfc2136handler.cc pdns-4.9.15/pdns/rfc2136handler.cc --- pdns-4.9.14/pdns/rfc2136handler.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/rfc2136handler.cc 2026-05-11 09:08:45.000000000 +0000 @@ -653,8 +653,19 @@ g_log<d_update_policy_lua == nullptr) { - + string fname = ::arg()["lua-dnsupdate-policy-script"]; + std::unique_ptr update_policy_lua; + if (!fname.empty()) { + try { + update_policy_lua = std::make_unique(); + update_policy_lua->loadFile(fname); + } + catch (const std::runtime_error& e) { + g_log< allowedRanges; B.getDomainMetadata(p.qdomain, "ALLOW-DNSUPDATE-FROM", allowedRanges); @@ -886,8 +897,8 @@ const DNSRecord *rr = &answer.first; if (rr->d_place == DNSResourceRecord::AUTHORITY) { /* see if it's permitted by policy */ - if (this->d_update_policy_lua != nullptr) { - if (this->d_update_policy_lua->updatePolicy(rr->d_name, QType(rr->d_type), di.zone, p) == false) { + if (update_policy_lua != nullptr) { + if (update_policy_lua->updatePolicy(rr->d_name, QType(rr->d_type), di.zone, p) == false) { g_log<d_name << "/" << QType(rr->d_type).toString() << ": Not permitted by policy"<d_class == QClass::NONE && rr->d_type == QType::NS && rr->d_name == di.zone) nsRRtoDelete.push_back(rr); - else if (rr->d_class == QClass::IN && rr->d_ttl > 0) { + else if (rr->d_class == QClass::IN) { if (rr->d_type == QType::CNAME) { cnamesToAdd.push_back(rr); } else { diff -Nru pdns-4.9.14/pdns/stubresolver.cc pdns-4.9.15/pdns/stubresolver.cc --- pdns-4.9.14/pdns/stubresolver.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/stubresolver.cc 2026-05-11 09:08:45.000000000 +0000 @@ -171,21 +171,27 @@ catch (...) { continue; } - MOADNSParser mdp(false, reply); - if (mdp.d_header.rcode == RCode::ServFail) { - continue; - } + try { + MOADNSParser mdp(false, reply); + if (mdp.d_header.rcode == RCode::ServFail) { + continue; + } - for (const auto& answer : mdp.d_answers) { - if (answer.first.d_place == 1 && answer.first.d_type == qtype) { - DNSZoneRecord zrr; - zrr.dr = answer.first; - zrr.auth = true; - ret.push_back(zrr); + for (const auto& answer : mdp.d_answers) { + if (answer.first.d_place == 1 && answer.first.d_type == qtype) { + DNSZoneRecord zrr; + zrr.dr = answer.first; + zrr.auth = true; + ret.push_back(std::move(zrr)); + } } + g_log << Logger::Debug << logPrefix << "Question for '" << queryNameType << "' got answered by " << dest.toString() << endl; + return mdp.d_header.rcode; + } + catch (const MOADNSException& exc) { + g_log << Logger::Debug << logPrefix << "Question for '" << queryNameType << "' got ill-formed answer from " << dest.toString() << ": " << exc.what() << endl; + continue; } - g_log << Logger::Debug << logPrefix << "Question for '" << queryNameType << "' got answered by " << dest.toString() << endl; - return mdp.d_header.rcode; } return RCode::ServFail; } diff -Nru pdns-4.9.14/pdns/webserver.cc pdns-4.9.15/pdns/webserver.cc --- pdns-4.9.14/pdns/webserver.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/webserver.cc 2026-05-11 09:08:45.000000000 +0000 @@ -67,10 +67,9 @@ string plain; B64Decode(cookie, plain); - vector cparts; - stringtok(cparts, plain, ":"); - - auth_ok = (cparts.size() == 2 && credentials.matches(cparts[1].c_str())); + if (auto colon = plain.find(":"); colon != std::string::npos) { + auth_ok = credentials.matches(plain.substr(colon + 1)); + } } return auth_ok; } diff -Nru pdns-4.9.14/pdns/ws-auth.cc pdns-4.9.15/pdns/ws-auth.cc --- pdns-4.9.14/pdns/ws-auth.cc 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/ws-auth.cc 2026-05-11 09:08:45.000000000 +0000 @@ -125,6 +125,10 @@ void AuthWebServer::go(StatBag& stats) { + // Compute a unique random value used for indexPOST validation + std::array buf{}; + dns_random(buf.data(), buf.size()); + d_unique = Base64Encode(std::string(buf.data(), buf.size())); S.doRings(); std::thread webT([this]() { webThread(); }); webT.detach(); @@ -175,7 +179,7 @@ return result; } -static void printtable(ostringstream& ret, const string& ringname, const string& title, int limit = 10) +static void printtable(ostringstream& ret, const string& ringname, const std::string& unique, const string& title, int limit = 10) { unsigned int tot = 0; int entries = 0; @@ -187,21 +191,38 @@ } ret << "
"; - ret << "Reset" << endl; + + // NOLINTBEGIN(modernize-raw-string-literal) + ret << ""; + ret << "
"; + ret << ""; + ret << ""; + ret << ""; + ret << "
"; + ret << "
" << endl; + ret << "

" << title << "

" << endl; ret << "
"; ret << "Showing: Top " << limit << " of " << entries << "" << endl; - ret << "Resize: "; - std::vector sizes{10, 100, 500, 1000, 10000, 500000, 0}; - for (int i = 0; sizes[i] != 0; ++i) { - if (S.getRingSize(ringname) != sizes[i]) { - ret << "" << sizes[i] << " "; - } - else { - ret << "(" << sizes[i] << ") "; - } - } + + ret << ""; + ret << "
"; + ret << ""; + ret << ""; + ret << ""; + ret << ""; + ret << "
"; ret << "
"; + // NOLINTEND(modernize-raw-string-literal) ret << ""; unsigned int printed = 0; @@ -246,26 +267,8 @@ return (boost::format("%.01f%%") % val).str(); } -void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp) +void AuthWebServer::indexGET(HttpRequest* req, HttpResponse* resp) { - if (!req->getvars["resetring"].empty()) { - if (S.ringExists(req->getvars["resetring"])) { - S.resetRing(req->getvars["resetring"]); - } - resp->status = 302; - resp->headers["Location"] = req->url.path; - return; - } - if (!req->getvars["resizering"].empty()) { - int size = std::stoi(req->getvars["size"]); - if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000) { - S.resizeRing(req->getvars["resizering"], std::stoi(req->getvars["size"])); - } - resp->status = 302; - resp->headers["Location"] = req->url.path; - return; - } - ostringstream ret; ret << "" << endl; @@ -301,10 +304,11 @@ ret << "Backend query load, 1, 5, 10 minute averages: " << std::setprecision(3) << (int)d_qcachemisses.get1() << ", " << (int)d_qcachemisses.get5() << ", " << (int)d_qcachemisses.get10() << ". Max queries/second: " << (int)d_qcachemisses.getMax() << "
" << endl; ret << "Total queries: " << S.read("udp-queries") << ". Question/answer latency: " << static_cast(S.read("latency")) / 1000.0 << "ms


" << endl; - if (req->getvars["ring"].empty()) { + auto ringname = req->getvars["ring"]; + if (ringname.empty()) { auto entries = S.listRings(); for (const auto& entry : entries) { - printtable(ret, entry, S.getRingTitle(entry)); + printtable(ret, entry, d_unique, S.getRingTitle(entry)); } printvars(ret); @@ -312,8 +316,8 @@ printargs(ret); } } - else if (S.ringExists(req->getvars["ring"])) { - printtable(ret, req->getvars["ring"], S.getRingTitle(req->getvars["ring"]), 100); + else if (S.ringExists(ringname)) { + printtable(ret, ringname, d_unique, S.getRingTitle(ringname), 100); } ret << "" << endl; @@ -324,6 +328,37 @@ resp->status = 200; } +void AuthWebServer::indexPOST(HttpRequest* req, HttpResponse* resp) +{ + string unique = req->postvars["unique"]; + if (unique != d_unique) { + throw HttpForbiddenException(); + } + + string ring = req->postvars["resetring"]; + if (!ring.empty()) { + if (S.ringExists(ring)) { + S.resetRing(ring); + } + resp->status = 302; + resp->headers["Location"] = req->url.path; + return; + } + + ring = req->postvars["resizering"]; + if (!ring.empty()) { + int size = std::stoi(req->postvars["size"]); + if (S.ringExists(ring) && size > 0 && size <= 500000 && S.getRingSize(ring) != static_cast(size)) { + S.resizeRing(ring, size); + } + resp->status = 302; + resp->headers["Location"] = req->url.path; + return; + } + + throw HttpForbiddenException(); +} + /** Helper to build a record content as needed. */ static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot) { @@ -2387,7 +2422,7 @@ } if (!new_records.empty() && ent_present) { QType qt_ent{QType::ENT}; - if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qt_ent, new_records)) { + if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qt_ent, {})) { throw ApiException("Hosting backend does not support editing records."); } } @@ -2928,6 +2963,7 @@ ret << ".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }" << endl; ret << ".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}" << endl; ret << ".resizering {float: right;}" << endl; + ret << "input, button { border: 0; padding: 0; background: inherit; text-decoration: underline; }" << endl; resp->body = ret.str(); resp->status = 200; } @@ -2977,10 +3013,13 @@ d_ws->registerApiHandler("/api", apiDiscovery, "GET"); } if (::arg().mustDo("webserver")) { + d_ws->registerWebHandler("/style.css", cssfunction, "GET"); + // These two handlers need to be able to access our classes' fields, + // hence the use of lambdas to capture this and invoke a class method. d_ws->registerWebHandler( - "/style.css", [](HttpRequest* req, HttpResponse* resp) { cssfunction(req, resp); }, "GET"); + "/", [this](HttpRequest* req, HttpResponse* resp) { indexGET(req, resp); }, "GET"); d_ws->registerWebHandler( - "/", [this](HttpRequest* req, HttpResponse* resp) { indexfunction(req, resp); }, "GET"); + "/", [this](HttpRequest* req, HttpResponse* resp) { indexPOST(req, resp); }, "POST"); d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET"); } d_ws->go(); diff -Nru pdns-4.9.14/pdns/ws-auth.hh pdns-4.9.15/pdns/ws-auth.hh --- pdns-4.9.14/pdns/ws-auth.hh 2026-04-08 09:58:17.000000000 +0000 +++ pdns-4.9.15/pdns/ws-auth.hh 2026-05-11 09:08:45.000000000 +0000 @@ -54,7 +54,8 @@ static string makePercentage(const double& val); private: - void indexfunction(HttpRequest* req, HttpResponse* resp); + void indexGET(HttpRequest* req, HttpResponse* resp); + void indexPOST(HttpRequest* req, HttpResponse* resp); void jsonstat(HttpRequest* req, HttpResponse* resp); void registerApiHandler(const string& url, std::function handler); void webThread(); @@ -65,6 +66,7 @@ Ewma d_queries, d_cachehits, d_cachemisses; Ewma d_qcachehits, d_qcachemisses; unique_ptr d_ws{nullptr}; + std::string d_unique; }; void apiDocs(HttpRequest* req, HttpResponse* resp);