]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth,rec: add ignore-unknown-settings setting
authorChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Thu, 29 Oct 2020 12:48:46 +0000 (13:48 +0100)
committerChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Wed, 31 Mar 2021 13:09:10 +0000 (15:09 +0200)
docs/settings.rst
pdns/arguments.cc
pdns/arguments.hh
pdns/misc.hh
pdns/recursordist/docs/settings.rst

index f45208dae906106e5559ec7e42c06c5f9374178e..8fa837b60d09479a7fd7565dc11d2fea5780bcd6 100644 (file)
@@ -681,6 +681,21 @@ or slave settings.
 
 Run within a guardian process. See :ref:`running-guardian`.
 
+.. _setting-ignore-unknown-settings:
+
+``ignore-unknown-settings``
+---------------------------
+
+.. versionadded:: 4.5.0
+
+-  Setting names, separated by commas
+-  Default: empty
+
+Names of settings to be ignored while parsing configuration files, if the setting
+name is unknown to PowerDNS.
+
+Useful during upgrade testing.
+
 .. _setting-include-dir:
 
 ``include-dir``
index aa21f6ded6cb9beb96efca93a8c84fb67b5a182a..73684cff676c98583ef879bba34e7c925080ac85 100644 (file)
 
 const ArgvMap::param_t::const_iterator ArgvMap::begin()
 {
-  return params.begin();
+  return d_params.begin();
 }
 
 const ArgvMap::param_t::const_iterator ArgvMap::end()
 {
-  return params.end();
+  return d_params.end();
 }
 
 string & ArgvMap::set(const string &var)
 {
-  return params[var];
+  return d_params[var];
 }
 
 void ArgvMap::setDefault(const string &var, const string &value)
@@ -58,9 +58,9 @@ void ArgvMap::setDefault(const string &var, const string &value)
 
 void ArgvMap::setDefaults()
 {
-  for(map<string,string>::const_iterator i=params.begin();i!=params.end();++i)
-    if(! defaultmap.count(i->first))
-      defaultmap.insert(*i);
+  for (const auto& i: d_params)
+    if(! defaultmap.count(i.first))
+      defaultmap.insert(i);
 }
 
 bool ArgvMap::mustDo(const string &var)
@@ -71,8 +71,8 @@ bool ArgvMap::mustDo(const string &var)
 vector<string>ArgvMap::list()
 {
   vector<string> ret;
-  for(map<string,string>::const_iterator i=params.begin();i!=params.end();++i)
-    ret.push_back(i->first);
+  for (const auto& i: d_params)
+    ret.push_back(i.first);
   return ret;
 }
 
@@ -105,13 +105,13 @@ string & ArgvMap::setSwitch(const string &var, const string &help)
 
 bool ArgvMap::contains(const string &var, const string &val)
 {
-  params_t::const_iterator param = params.find(var);
-  if(param == params.end() || param->second.empty())  {
+  const auto& param = d_params.find(var);
+  if(param == d_params.end() || param->second.empty())  {
     return false;
   }
   vector<string> parts;
   
-  stringtok( parts, param->second, ", \t" );
+  stringtok(parts, param->second, ", \t");
   for (const auto& part: parts) {
     if (part == val) {
       return true;
@@ -154,6 +154,40 @@ string ArgvMap::helpstring(string prefix)
   return help;
 }
 
+const string ArgvMap::formatOne(bool running, bool full, const string &var, const string &help, const string& theDefault, const string& current)
+{
+  string out;
+
+  if (!running || full) {
+    out += "#################################\n";
+    out += "# ";
+    out += var;
+    out += "\t";
+    out += help;
+    out += "\n#\n";
+  } else {
+    if (theDefault == current) {
+      return "";
+    }
+  }
+
+  if (! running || theDefault == current) {
+    out += "# ";
+  }
+
+  if (running) {
+    out += var + "=" + current + "\n";
+    if (full) {
+      out += "\n";
+    }
+  } else {
+    out += var + "=" + theDefault + "\n\n";
+  }
+
+  return out;
+}
+
+// If running and full, only changed settings are returned.
 string ArgvMap::configstring(bool running, bool full)
 {
   string help;
@@ -163,40 +197,28 @@ string ArgvMap::configstring(bool running, bool full)
   else
     help="# Autogenerated configuration file template\n\n";
 
+  // Affects parsing, should come first.
+  help += formatOne(running, full, "ignore-unknown-settings", helpmap["ignore-unknown-settings"], defaultmap["ignore-unknown-settings"], d_params["ignore-unknown-settings"]);
+
   for(const auto& i: helpmap) {
-    if(d_typeMap[i.first]=="Command")
+    if (d_typeMap[i.first] == "Command")
+      continue;
+    if (i.first == "ignore-unknown-settings")
       continue;
 
-    if (! defaultmap.count(i.first)) {
-      throw ArgException(string("Default for parameter '")+i.first+"' not set");
+    if (!defaultmap.count(i.first)) {
+      throw ArgException(string("Default for setting '")+i.first+"' not set");
     }
 
-    if (!running || full) {
-      help+="#################################\n";
-      help+="# ";
-      help+=i.first;
-      help+="\t";
-      help+=i.second;
-      help+="\n#\n";
-    } else {
-      if (defaultmap[i.first] == params[i.first]) {
-        continue;
-      }
-    }
-
-    if (! running || defaultmap[i.first] == params[i.first]) {
-      help+="# ";
-    }
+    help += formatOne(running, full, i.first, i.second, defaultmap[i.first], d_params[i.first]);
+  }
 
-    if (running) {
-      help+=i.first+"="+params[i.first]+"\n";
-      if (full) {
-        help+="\n";
-      }
-    } else {
-      help+=i.first+"="+defaultmap[i.first]+"\n\n";
+  if (running) {
+    for(const auto& i: d_unknownParams) {
+      help += formatOne(running, full, i.first, "unknown setting", "", i.second);
     }
   }
+
   return help;
 }
 
@@ -205,7 +227,7 @@ const string & ArgvMap::operator[](const string &arg)
   if(!parmIsset(arg))
     throw ArgException(string("Undefined but needed argument: '")+arg+"'");
 
-  return params[arg];
+  return d_params[arg];
 }
 
 mode_t ArgvMap::asMode(const string &arg) 
@@ -217,7 +239,7 @@ mode_t ArgvMap::asMode(const string &arg)
   if(!parmIsset(arg))
    throw ArgException(string("Undefined but needed argument: '")+arg+"'");
 
-  cptr_orig = params[arg].c_str();
+  cptr_orig = d_params[arg].c_str();
   mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8));
   if (mode == 0 && cptr_ret == cptr_orig) 
     throw ArgException("'" + arg + string("' contains invalid octal mode"));
@@ -233,11 +255,11 @@ gid_t ArgvMap::asGid(const string &arg)
   if(!parmIsset(arg))
    throw ArgException(string("Undefined but needed argument: '")+arg+"'");
 
-  cptr_orig = params[arg].c_str();
+  cptr_orig = d_params[arg].c_str();
   gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0));
   if (gid == 0 && cptr_ret == cptr_orig) {
     // try to resolve
-    struct group *group = getgrnam(params[arg].c_str());
+    struct group *group = getgrnam(d_params[arg].c_str());
     if (group == nullptr)
      throw ArgException("'" + arg + string("' contains invalid group"));
     gid = group->gr_gid;
@@ -254,11 +276,11 @@ uid_t ArgvMap::asUid(const string &arg)
   if(!parmIsset(arg))
    throw ArgException(string("Undefined but needed argument: '")+arg+"'");
 
-  cptr_orig = params[arg].c_str();
+  cptr_orig = d_params[arg].c_str();
   uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
   if (uid == 0 && cptr_ret == cptr_orig) {
     // try to resolve
-    struct passwd *pwent = getpwnam(params[arg].c_str());
+    struct passwd *pwent = getpwnam(d_params[arg].c_str());
     if (pwent == nullptr)
      throw ArgException("'" + arg + string("' contains invalid group"));
     uid = pwent->pw_uid;
@@ -276,10 +298,10 @@ int ArgvMap::asNum(const string &arg, int def)
     throw ArgException(string("Undefined but needed argument: '")+arg+"'");
 
   // use default for empty values
-  if (params[arg].empty())
+  if (d_params[arg].empty())
    return def;
 
-  cptr_orig = params[arg].c_str();
+  cptr_orig = d_params[arg].c_str();
   retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
   if (!retval && cptr_ret == cptr_orig)
    throw ArgException("'"+arg+"' value '"+string(cptr_orig) + string( "' is not a valid number"));
@@ -291,7 +313,7 @@ bool ArgvMap::isEmpty(const string &arg)
 {
    if(!parmIsset(arg))
     return true;
-   return params[arg].empty();
+   return d_params[arg].empty();
 }
 
 double ArgvMap::asDouble(const string &arg)
@@ -303,10 +325,10 @@ double ArgvMap::asDouble(const string &arg)
   if(!parmIsset(arg))
     throw ArgException(string("Undefined but needed argument: '")+arg+"'");
 
-  if (params[arg].empty())
+  if (d_params[arg].empty())
    return 0.0;
 
-  cptr_orig = params[arg].c_str();
+  cptr_orig = d_params[arg].c_str();
   retval = strtod(cptr_orig, &cptr_ret);
  
   if (retval == 0 && cptr_ret == cptr_orig)
@@ -317,12 +339,12 @@ double ArgvMap::asDouble(const string &arg)
 
 ArgvMap::ArgvMap()
 {
-
+  set("ignore-unknown-settings","Configuration settings to ignore if they are unknown")="";
 }
 
 bool ArgvMap::parmIsset(const string &var)
 {
-  return (params.find(var)!=params.end());
+  return d_params.find(var) != d_params.end();
 }
 
 void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax)
@@ -365,23 +387,36 @@ void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax)
     {
       if(incremental)
       {
-        if(params[var].empty())
+        if(d_params[var].empty())
         {
           if(!d_cleared.count(var))
-            throw ArgException("Incremental parameter '"+var+"' without a parent");
-          params[var]=val;
+            throw ArgException("Incremental setting '"+var+"' without a parent");
+          d_params[var] = val;
         }
         else
-          params[var]+=", " + val;
+          d_params[var] += ", " + val;
       }
       else
       {
-        params[var]=val;
+        d_params[var] = val;
         d_cleared.insert(var);
       }
     }
-    else if(!lax)
-      throw ArgException("Trying to set unknown parameter '"+var+"'");
+    else
+    {
+      // unknown setting encountered. see if its on the ignore list before throwing.
+      std::set<string> parts;
+      stringtok(parts, d_params["ignore-unknown-settings"], " ,\t\n\r");
+      if (parts.find(var) != parts.end()) {
+        d_unknownParams[var] = val;
+        g_log<<Logger::Warning<<"Ignoring unknown setting '"<<var<<"' as requested"<<endl;
+        return;
+      }
+
+      if (!lax) {
+        throw ArgException("Trying to set unknown setting '"+var+"'");
+      }
+    }
   }
 }
 
@@ -454,7 +489,7 @@ bool ArgvMap::parseFile(const char *fname, const string& arg, bool lax) {
 
 bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault)
 {
-  params[arg]=theDefault;
+  d_params[arg] = theDefault;
 
   return parseFile(fname, arg, false);
 }
@@ -475,7 +510,7 @@ bool ArgvMap::file(const char *fname, bool lax, bool included)
   }
 
   // handle include here (avoid re-include)
-  if (!included && !params["include-dir"].empty()) {
+  if (!included && !d_params["include-dir"].empty()) {
     std::vector<std::string> extraConfigs;
     gatherIncludes(extraConfigs); 
     for(const std::string& fn :  extraConfigs) {
@@ -491,13 +526,13 @@ bool ArgvMap::file(const char *fname, bool lax, bool included)
 
 void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) {
   extraConfigs.clear();
-  if (params["include-dir"].empty())
+  if (d_params["include-dir"].empty())
     return; // nothing to do
 
   DIR *dir;
-  if (!(dir = opendir(params["include-dir"].c_str()))) {
+  if (!(dir = opendir(d_params["include-dir"].c_str()))) {
     int err = errno;
-    string msg = params["include-dir"] + " is not accessible: " + strerror(err);
+    string msg = d_params["include-dir"] + " is not accessible: " + strerror(err);
     g_log << Logger::Error << msg << std::endl;
     throw ArgException(msg);
   }
@@ -508,7 +543,7 @@ void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) {
       continue; // skip any dots
     if (boost::ends_with(ent->d_name, ".conf")) {
       // build name
-      string name = params["include-dir"] + "/" + ent->d_name; // FIXME: Use some path separator
+      string name = d_params["include-dir"] + "/" + ent->d_name; // FIXME: Use some path separator
       // ensure it's readable file
       struct stat st;
       if (stat(name.c_str(), &st) || !S_ISREG(st.st_mode)) {
index 177224163873fe8597e30f4aae29138c41341816..696f3ffa9257a0108038a06dfa647658fa040579 100644 (file)
@@ -120,8 +120,9 @@ public:
   void gatherIncludes(std::vector<std::string> &extraConfigs);
 private:
   void parseOne(const string &unparsed, const string &parseOnly="", bool lax=false);
-  typedef map<string,string> params_t;
-  params_t params;
+  const string formatOne(bool running, bool full, const string &var, const string &help, const string& theDefault, const string& value);
+  map<string,string> d_params;
+  map<string,string> d_unknownParams;
   map<string,string> helpmap;
   map<string,string> defaultmap;
   map<string,string> d_typeMap;
index 193750446ca2573ba77ebed3fb24dc11c3c7d675..b283992cf3a9cdc61421242379ca10638b544aa0 100644 (file)
@@ -80,10 +80,16 @@ struct ServiceTuple
 };
 void parseService(const string &descr, ServiceTuple &st);
 
-template <typename Container>
+template<typename Container, class Enable = void>
 void
 stringtok (Container &container, string const &in,
-           const char * const delimiters = " \t\n")
+           const char * const delimiters = " \t\n");
+
+template<typename Container,
+         typename std::enable_if<std::is_function<typename Container::push_back>::value>::type = true>
+void
+stringtok (Container &container, string const &in,
+           const char * const delimiters)
 {
   const string::size_type len = in.length();
   string::size_type i = 0;
@@ -109,6 +115,36 @@ stringtok (Container &container, string const &in,
   }
 }
 
+template<typename Container,
+         typename std::enable_if<std::is_function<typename Container::insert>::value>::type = true>
+void
+stringtok (Container &container, string const &in,
+           const char * const delimiters)
+{
+  const string::size_type len = in.length();
+  string::size_type i = 0;
+
+  while (i<len) {
+    // eat leading whitespace
+    i = in.find_first_not_of (delimiters, i);
+    if (i == string::npos)
+      return;   // nothing left but white space
+
+    // find the end of the token
+    string::size_type j = in.find_first_of (delimiters, i);
+
+    // push token
+    if (j == string::npos) {
+      container.insert (in.substr(i));
+      return;
+    } else
+      container.insert (in.substr(i, j-i));
+
+    // set up for next loop
+    i = j + 1;
+  }
+}
+
 template<typename T> bool rfc1982LessThan(T a, T b)
 {
   static_assert(std::is_unsigned<T>::value, "rfc1982LessThan only works for unsigned types");
index f4d796bd3d79483ef79cb173531b1cc920b25c7d..5914ce1b954ccace41edfb7f141570fddf3136af 100644 (file)
@@ -794,6 +794,21 @@ If set, EDNS options in incoming queries are extracted and passed to the :func:`
 
 If set, the root-hints are read from this file. If unset, default root hints are used.
 
+.. _setting-ignore-unknown-settings:
+
+``ignore-unknown-settings``
+---------------------------
+
+.. versionadded:: 4.6.0
+
+-  Setting names, separated by commas
+-  Default: empty
+
+Names of settings to be ignored while parsing configuration files, if the setting
+name is unknown to PowerDNS.
+
+Useful during upgrade testing.
+
 .. _setting-include-dir:
 
 ``include-dir``