]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Handle ENT records (with no content) in pipe backend protocol. 16185/head
authorMiod Vallat <miod.vallat@powerdns.com>
Fri, 26 Sep 2025 14:59:25 +0000 (16:59 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Fri, 26 Sep 2025 15:54:38 +0000 (17:54 +0200)
Fixes: #15027
Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
docs/backends/pipe.rst
modules/pipebackend/pipebackend.cc
modules/pipebackend/pipebackend.hh

index beac6130af62cd10e9624f350f67aa68c4dbb11f..0ff863c80d2bfbfd27649263e309ffe5bbfa2a34 100644 (file)
@@ -41,7 +41,7 @@ DNS-based failover with low TTLs.
 .. note::
   Please do read the :doc:`Backend Writer's guide <../appendices/backend-writers-guide>` carefully. The
   PipeBackend, like all other backends, must not do any DNS thinking, but
-  answer all questions (INCLUDING THE ANY QUESTION) faithfully.
+  answer all questions (**including the ANY question**) faithfully.
   Specifically, the queries that the PipeBackend receives will not
   correspond to the queries that arrived over DNS. So, a query for an AAAA
   record may turn into a backend query for an ANY record. There is nothing
@@ -66,7 +66,8 @@ local-ip-address field is added after the remote-ip-address, the
 local-ip-address refers to the IP address the question was received on.
 When set to 3, the real remote IP/subnet is added based on edns-subnet
 support (this also requires enabling :ref:`setting-edns-subnet-processing`).
-When set to 4 it sends zone name in AXFR request. See also :ref:`PipeBackend Protocol <pipebackend-protocol>` below.
+When set to 4, it will also send the zone name in AXFR requests.
+See also :ref:`PipeBackend Protocol <pipebackend-protocol>` below.
 
 .. _setting-pipe-command:
 
@@ -119,13 +120,15 @@ characters.
 Handshake
 ^^^^^^^^^
 
-PowerDNS sends out ``HELO\t1``, indicating that it wants to speak the
-protocol as defined in this document, version 1. For abi-version 2 or 3,
-PowerDNS sends ``HELO\t2`` or ``HELO\t3``. A PowerDNS Coprocess must
-then send out a banner, prefixed by ``OK\t``, indicating it launched
-successfully. If it does not support the indicated version, it should
-respond with ``FAIL``, but not exit. Suggested behaviour is to try and
-read a further line, and wait to be terminated.
+PowerDNS sends out ``HELO\tN``, where N is the version of the protocol it
+wants to speak (e.g. ``HELO\t1`` for protocol version 1, ``HELO\t2`` for
+protocol version 2, etc).
+A PowerDNS Coprocess must then send out a banner, prefixed by ``OK\t``,
+indicating it launched successfully.
+If it does not support the indicated version, it should respond with ``FAIL``,
+but not exit.
+Suggested behaviour is to try and read a further line, and wait to be
+terminated.
 
 .. note::
   Fields are separated by a tab (``\t``) character,
@@ -150,8 +153,8 @@ pipe-abi-version = 2
 
     Q   qname       qclass  qtype   id  remote-ip-address   local-ip-address
 
-pipe-abi-version = 3
-~~~~~~~~~~~~~~~~~~~~
+pipe-abi-version = 3 and higher
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 ::
 
@@ -193,7 +196,7 @@ AXFR-queries look like this:
     AXFR    id  zone-name
 
 The ``id`` is gathered from the answer to a SOA query. ``zone-name`` is
-given in ABI version 4.
+given in ABI version 4 and higher.
 
 Answers
 ^^^^^^^
@@ -223,6 +226,7 @@ Again, all fields are tab-separated.
 ``content`` is as specified in :doc:`../appendices/types`. For MX and SRV,
 content consists of the priority, followed by a tab, followed by the
 actual content.
+For ENT (Empty Non-Terminal), content will be ignored and can be omitted.
 
 A sample dialogue may look like this (note that in reality, almost all
 queries will actually be for the ANY qtype):
@@ -265,7 +269,7 @@ This is a typical zone transfer.
 ABI version 3 and higher
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
-For abi-version 3, DATA-responses get two extra fields:
+From abi-version 3 onwards, DATA-responses get two extra fields:
 
 ::
 
@@ -284,7 +288,7 @@ which are used for delegation, and also for any glue (A, AAAA) records
 present for this purpose. Do note that the DS record for a secure
 delegation should be authoritative!
 
-For abi-versions 1 and 2, the two new fields fall back to default
+For abi-version 1 and 2, the two new fields fall back to default
 values. The default value for scopebits is 0. The default for auth is 1
 (meaning authoritative).
 
index 431b876922e3450f2edb4ef5c3115315aece6861..e6b6d3d539faf6e4aeee95b55ba26b664f7a6555 100644 (file)
@@ -266,6 +266,12 @@ PipeBackend::~PipeBackend()
   cleanup();
 }
 
+void PipeBackend::throwTooShortDataError(const std::string& what)
+{
+  g_log << Logger::Error << kBackendId << " Coprocess returned incomplete or empty line in data section for query for " << d_qname << endl;
+  throw PDNSException("Format error communicating with coprocess in data section" + what);
+}
+
 bool PipeBackend::get(DNSResourceRecord& r)
 {
   if (d_disavow) // this query has been blocked
@@ -275,9 +281,6 @@ bool PipeBackend::get(DNSResourceRecord& r)
 
   // The answer format:
   // DATA    qname           qclass  qtype   ttl     id      content
-  unsigned int extraFields = 0;
-  if (d_abiVersion >= 3)
-    extraFields = 2;
 
   try {
     launch();
@@ -300,40 +303,49 @@ bool PipeBackend::get(DNSResourceRecord& r)
         continue;
       }
       else if (parts[0] == "DATA") { // yay
-        if (parts.size() < 7 + extraFields) {
-          g_log << Logger::Error << kBackendId << " Coprocess returned incomplete or empty line in data section for query for " << d_qname << endl;
-          throw PDNSException("Format error communicating with coprocess in data section");
-          // now what?
+        // The shortest records (ENT) require 6 fields. Other may require more
+        // and will have a stricter check once the record type has been
+        // computed.
+        if (parts.size() < 6 + (d_abiVersion >= 3 ? 2 : 0)) {
+          throwTooShortDataError("");
         }
 
         if (d_abiVersion >= 3) {
           r.scopeMask = std::stoi(parts[1]);
           r.auth = (parts[2] == "1");
+          parts.erase(parts.begin() + 1, parts.begin() + 3);
         }
         else {
           r.scopeMask = 0;
           r.auth = true;
         }
-        r.qname = DNSName(parts[1 + extraFields]);
-        r.qtype = parts[3 + extraFields];
-        pdns::checked_stoi_into(r.ttl, parts[4 + extraFields]);
-        pdns::checked_stoi_into(r.domain_id, parts[5 + extraFields]);
-
-        if (r.qtype.getCode() != QType::MX && r.qtype.getCode() != QType::SRV) {
+        r.qname = DNSName(parts[1]);
+        r.qtype = parts[3];
+        pdns::checked_stoi_into(r.ttl, parts[4]);
+        pdns::checked_stoi_into(r.domain_id, parts[5]);
+
+        switch (r.qtype.getCode()) {
+        case QType::ENT:
+          // No other data to process
           r.content.clear();
-          for (unsigned int n = 6 + extraFields; n < parts.size(); ++n) {
-            if (n != 6 + extraFields)
-              r.content.append(1, ' ');
-            r.content.append(parts[n]);
+          break;
+        case QType::MX:
+        case QType::SRV:
+          if (parts.size() < 8) {
+            throwTooShortDataError("of MX/SRV record");
           }
-        }
-        else {
-          if (parts.size() < 8 + extraFields) {
-            g_log << Logger::Error << kBackendId << " Coprocess returned incomplete MX/SRV line in data section for query for " << d_qname << endl;
-            throw PDNSException("Format error communicating with coprocess in data section of MX/SRV record");
+          r.content = parts[6] + " " + parts[7];
+          break;
+        default:
+          if (parts.size() < 7) {
+            throwTooShortDataError("");
           }
-
-          r.content = parts[6 + extraFields] + " " + parts[7 + extraFields];
+          r.content = parts[6];
+          for (std::vector<std::string>::size_type pos = 7; pos < parts.size(); ++pos) {
+            r.content.append(1, ' ');
+            r.content.append(parts[pos]);
+          }
+          break;
         }
         break;
       }
index dd75e6c3da3f6ea3b00595df769d8f735539b638..15535f4953230ccd5953e6a9254175880a635cbf 100644 (file)
@@ -61,6 +61,8 @@ public:
 private:
   void launch();
   void cleanup();
+  void throwTooShortDataError(const std::string& what);
+
   std::unique_ptr<CoWrapper> d_coproc;
   std::unique_ptr<Regex> d_regex;
   DNSName d_qname;