]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Perform character set validation of view names. 16210/head
authorMiod Vallat <miod.vallat@powerdns.com>
Mon, 22 Sep 2025 08:42:47 +0000 (10:42 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Fri, 3 Oct 2025 14:48:00 +0000 (16:48 +0200)
Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
(cherry picked from commit b260728c8517d205636471e42deb3b8ffb7db664)

docs/views.rst
meson.build
pdns/Makefile.am
pdns/check-zone.cc [new file with mode: 0644]
pdns/check-zone.hh [new file with mode: 0644]
pdns/pdnsutil.cc
pdns/ws-auth.cc
regression-tests/tests/views-management/expected_result

index f28ddba8804b0124440591437d3d412bf56e63ab..3fb83ef5f77f034652b2e22090b968c2278302cb 100644 (file)
@@ -85,6 +85,10 @@ that view, as their variantless contents.
 Only one variant per zone may appear in a view; setting a new zone variant will
 replace the previous one in the view.
 
+View names are case-sensitive and may be composed of letters, digits, spaces,
+as well as `-` (dash), `.` (dot) and `_` (underscore). They are not allowed to
+start with a dot or a space.
+
 Resolution Algorithm
 --------------------
 
index 4b6aa8b85007f8910fed188daf58224987ce7e63..9763f84b04d4e4de8be66548f0a95acd1ece9f5a 100644 (file)
@@ -497,6 +497,8 @@ common_sources += files(
   src_dir / 'bindparserclasses.hh',
   src_dir / 'burtle.hh',
   src_dir / 'cachecleaner.hh',
+  src_dir / 'check-zone.cc',
+  src_dir / 'check-zone.hh',
   src_dir / 'circular_buffer.hh',
   src_dir / 'comment.hh',
   src_dir / 'communicator.cc',
index 8dd8e5c3f70f97ade600b0652e621ca1cf77e722..ac6c0c3362df875bf2e24050edafc827140496db 100644 (file)
@@ -204,6 +204,7 @@ pdns_server_SOURCES = \
        bindparser.cc \
        burtle.hh \
        cachecleaner.hh \
+       check-zone.cc check-zone.hh \
        circular_buffer.hh \
        comment.hh \
        communicator.cc communicator.hh \
@@ -344,6 +345,7 @@ pdnsutil_SOURCES = \
        bindlexer.l \
        bindparser.yy \
        cachecleaner.hh \
+       check-zone.cc check-zone.hh \
        circular_buffer.hh \
        credentials.cc credentials.hh \
        dbdnsseckeeper.cc \
diff --git a/pdns/check-zone.cc b/pdns/check-zone.cc
new file mode 100644 (file)
index 0000000..5d6ba54
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dns.hh"
+#include "dnsrecords.hh"
+
+#include "check-zone.hh"
+
+namespace Check
+{
+
+bool validateViewName(std::string_view name, std::string& error)
+{
+  if (name.empty()) {
+    error = "Empty view names are not allowed";
+    return false;
+  }
+
+  if (auto pos = name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 _-."); pos != std::string_view::npos) {
+    error = std::string("View name contains forbidden character '") + name[pos] + "' at position " + std::to_string(pos);
+    return false;
+  }
+
+  if (name[0] == '.') {
+    error = "View names are not allowed to start with a dot";
+    return false;
+  }
+
+  if (name[0] == ' ') {
+    error = "View names are not allowed to start with a space";
+    return false;
+  }
+
+  return true;
+}
+
+} // namespace Check
diff --git a/pdns/check-zone.hh b/pdns/check-zone.hh
new file mode 100644 (file)
index 0000000..7009ad5
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+// These validation/verification routines are used by both pdnsutil and
+// the pdns_server REST API.
+// They build error messages, if any, into an object provided by the caller
+// (preferrably a container if it makes sense to report multiple errors);
+// it's up to each caller to decide how to report such errors.
+
+namespace Check
+{
+
+// Validate a view name. Although view names never appear on the wire, we
+// restrict them to [a-zA-Z0-9-_. ], with empty names or names with leading
+// whitespace or a leading dot forbidden.
+bool validateViewName(std::string_view name, std::string& error);
+
+} // namespace Check
index 1f4517c9c75f9dc1ab6495e698931854b24fd976..b67c84ea51c7df2f5d7b4915ba73e30aa67a44df 100644 (file)
@@ -12,6 +12,7 @@
 #include <csignal>
 #include <sys/wait.h>
 
+#include "check-zone.hh"
 #include "credentials.hh"
 #include "dnsseckeeper.hh"
 #include "dnssecinfra.hh"
@@ -5225,6 +5226,11 @@ static int listView(vector<string>& cmds, const std::string_view synopsis)
 
   UtilBackend B("default"); //NOLINT(readability-identifier-length)
 
+  if ((B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    cerr << "None of the configured backends support views." << endl;
+    return 1;
+  }
+
   vector<ZoneName> ret;
   B.viewListZones(cmds.at(0), ret);
 
@@ -5242,6 +5248,12 @@ static int listViews(vector<string>& cmds, const std::string_view synopsis)
 
   UtilBackend B("default"); //NOLINT(readability-identifier-length)
 
+  if ((B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    // Don't complain about the lack of view support in this case, but
+    // don't list anything either.
+    return 0;
+  }
+
   vector<string> ret;
   B.viewList(ret);
 
@@ -5259,7 +5271,17 @@ static int viewAddZone(vector<string>& cmds, const std::string_view synopsis)
 
   UtilBackend B("default"); //NOLINT(readability-identifier-length)
 
+  if ((B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    cerr << "None of the configured backends support views." << endl;
+    return 1;
+  }
+
   string view{cmds.at(0)};
+  string error;
+  if (!Check::validateViewName(view, error)) {
+    cerr << error << "." << endl;
+    return 1;
+  }
   ZoneName zone{cmds.at(1)};
   if (!B.viewAddZone(view, zone)) {
     cerr<<"Operation failed."<<endl;
@@ -5283,7 +5305,17 @@ static int viewDelZone(vector<string>& cmds, const std::string_view synopsis)
 
   UtilBackend B("default"); //NOLINT(readability-identifier-length)
 
+  if ((B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    cerr << "None of the configured backends support views." << endl;
+    return 1;
+  }
+
   string view{cmds.at(0)};
+  string error;
+  if (!Check::validateViewName(view, error)) {
+    cerr << error << "." << endl;
+    return 1;
+  }
   ZoneName zone{cmds.at(1)};
   if (!B.viewDelZone(view, zone)) {
     cerr<<"Operation failed."<<endl;
@@ -5300,6 +5332,11 @@ static int listNetwork(vector<string>& cmds, const std::string_view synopsis)
 
   UtilBackend B("default"); //NOLINT(readability-identifier-length)
 
+  if ((B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    cerr << "None of the configured backends support views." << endl;
+    return 1;
+  }
+
   vector<pair<Netmask, string> > ret;
 
   B.networkList(ret);
@@ -5318,6 +5355,11 @@ static int setNetwork(vector<string>& cmds, const std::string_view synopsis)
 
   UtilBackend B("default"); //NOLINT(readability-identifier-length)
 
+  if ((B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    cerr << "None of the configured backends support views." << endl;
+    return 1;
+  }
+
   Netmask net{cmds.at(0)};
   string view{};
   if (cmds.size() > 1) {
index 47ac61eecd1c5139c6b61c021a76f75442e828da..c2e82b6b75b396778d65edfb866b3aedcb2fd4ff 100644 (file)
@@ -53,6 +53,7 @@
 #include "auth-zonecache.hh"
 #include "threadname.hh"
 #include "tsigutils.hh"
+#include "check-zone.hh"
 
 using json11::Json;
 
@@ -2751,6 +2752,10 @@ static void apiServerViewsPOST(HttpRequest* req, HttpResponse* resp)
     throw ApiException("Zone " + zonename.toString() + " does not exist");
   }
   std::string view{req->parameters["view"]};
+  std::string error;
+  if (!Check::validateViewName(view, error)) {
+    throw ApiException(error);
+  }
 
   if (!domainInfo.backend->viewAddZone(view, zonename)) {
     throw ApiException("Failed to add " + zonename.toString() + " to view " + view);
@@ -2775,6 +2780,10 @@ static void apiServerViewsDELETE(HttpRequest* req, HttpResponse* resp)
 {
   ZoneData zoneData{req};
   std::string view{req->parameters["view"]};
+  std::string error;
+  if (!Check::validateViewName(view, error)) {
+    throw ApiException(error);
+  }
 
   if (!zoneData.domainInfo.backend->viewDelZone(view, zoneData.zoneName)) {
     throw ApiException("Failed to remove " + zoneData.zoneName.toString() + " from view " + view);
index 966b8a2fddbc6343da15b97cecceb988677cab91..dc26423849778ed24e1df453f562ea6a9c3f9e99 100644 (file)
@@ -1 +1 @@
-Operation failed.
+None of the configured backends support views.