]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth: add catalog to DomainInfo
authorKees Monshouwer <mind04@monshouwer.org>
Mon, 27 Jun 2022 08:59:49 +0000 (10:59 +0200)
committermind04 <mind04@monshouwer.org>
Fri, 8 Jul 2022 10:31:19 +0000 (12:31 +0200)
19 files changed:
modules/gmysqlbackend/4.3.0_to_4.7.0_schema.mysql.sql
modules/gmysqlbackend/gmysqlbackend.cc
modules/gmysqlbackend/schema.mysql.sql
modules/godbcbackend/4.3.0_to_4.7.0_schema.mssql.sql
modules/godbcbackend/godbcbackend.cc
modules/godbcbackend/schema.mssql.sql
modules/gpgsqlbackend/4.3.0_to_4.7.0_schema.pgsql.sql
modules/gpgsqlbackend/gpgsqlbackend.cc
modules/gpgsqlbackend/schema.pgsql.sql
modules/gsqlite3backend/4.3.1_to_4.7.0_schema.sqlite3.sql
modules/gsqlite3backend/gsqlite3backend.cc
modules/gsqlite3backend/schema.sqlite3.sql
modules/lmdbbackend/lmdbbackend.cc
modules/lmdbbackend/lmdbbackend.hh
pdns/backends/gsql/gsqlbackend.cc
pdns/backends/gsql/gsqlbackend.hh
pdns/backends/gsql/ssql.hh
pdns/dnsbackend.hh
pdns/pdnsutil.cc

index 97fd3c465f8324fe4b6d5eb33d6148432261f284..bb0956d0b75dcce827d58932f89daf0fd186fa70 100644 (file)
@@ -1 +1,4 @@
 ALTER TABLE domains ADD options VARCHAR(64000) DEFAULT NULL AFTER notified_serial;
+ALTER TABLE domains ADD catalog VARCHAR(255) DEFAULT NULL AFTER options;
+
+CREATE INDEX catalog_idx ON domains(catalog);
index 0b6073a6d30280b5937ab5106f1c352aac83d004..af3bd54ea2440190fc6d988328c1a78d5da1a3d5 100644 (file)
@@ -101,7 +101,7 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=? and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=? and name=? and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,account from domains where name=?");
+    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?");
 
     declare(suffix, "info-all-slaves-query", "", "select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
@@ -128,6 +128,7 @@ public:
     declare(suffix, "update-master-query", "", "update domains set master=? where name=?");
     declare(suffix, "update-kind-query", "", "update domains set type=? where name=?");
     declare(suffix, "update-options-query", "", "update domains set options=? where name=?");
+    declare(suffix, "update-catalog-query", "", "update domains set catalog=? where name=?");
     declare(suffix, "update-account-query", "", "update domains set account=? where name=?");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
index 6d7c17890d4c1d8904b4dfffc20f4ea84216ba1a..554786aee1709560767f598d75487f847cd50e32 100644 (file)
@@ -6,11 +6,13 @@ CREATE TABLE domains (
   type                  VARCHAR(6) NOT NULL,
   notified_serial       INT UNSIGNED DEFAULT NULL,
   options               VARCHAR(64000) DEFAULT NULL,
+  catalog               VARCHAR(255) DEFAULT NULL,
   account               VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL,
   PRIMARY KEY (id)
 ) Engine=InnoDB CHARACTER SET 'latin1';
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
 
 
 CREATE TABLE records (
index 8babd7aa98d574b531a5a0043d26248b852035f0..b874edf3480dcd0ae82f912015a8b53d1a892024 100644 (file)
@@ -1 +1,4 @@
 ALTER TABLE domains ADD COLUMN options VARCHAR(MAX) DEFAULT NULL;
+ALTER TABLE domains ADD COLUMN catalog VARCHAR(255) DEFAULT NULL;
+
+CREATE INDEX catalog_idx ON domains(catalog);
index 055dedf6973bdd40db73fb08c2342a674ce703c0..60572cf5acd87e922d20ac9aeaa5f1dc76d94628 100644 (file)
@@ -81,7 +81,7 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=? and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=? and name=? and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,account from domains where name=?");
+    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?");
 
     declare(suffix, "info-all-slaves-query", "", "select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
@@ -108,6 +108,7 @@ public:
     declare(suffix, "update-master-query", "", "update domains set master=? where name=?");
     declare(suffix, "update-kind-query", "", "update domains set type=? where name=?");
     declare(suffix, "update-options-query", "", "update domains set options=? where name=?");
+    declare(suffix, "update-catalog-query", "", "update domains set catalog=? where name=?");
     declare(suffix, "update-account-query", "", "update domains set account=? where name=?");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
index 1646d4e665dd4363fb6dc171ae9a8682fe0f2fb5..14e4212370528e46c904c96265ef703068d9f935 100644 (file)
@@ -6,11 +6,14 @@ CREATE TABLE domains (
   type                  VARCHAR(6) NOT NULL,
   notified_serial       INT DEFAULT NULL,
   options               VARCHAR(MAX) DEFAULT NULL,
+  catalog               VARCHAR(255) DEFAULT NULL,
   account               VARCHAR(40) DEFAULT NULL,
   PRIMARY KEY (id)
 );
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
+
 
 CREATE TABLE records (
   id                    INT IDENTITY,
index ed9b6e2faa725b75ef719942b6069610ac15eb26..420ec1d79ab339bab9d46bf22b6a7cda8a75c491 100644 (file)
@@ -1,8 +1,11 @@
 BEGIN;
   ALTER TABLE domains ADD COLUMN options VARCHAR(65535) DEFAULT NULL;
+  ALTER TABLE domains ADD COLUMN catalog VARCHAR(255) DEFAULT NULL;
 
   ALTER TABLE domains ADD COLUMN account_new VARCHAR(40) DEFAULT NULL;
   UPDATE domains SET account_new = account;
   ALTER TABLE domains DROP COLUMN account;
   ALTER TABLE domains RENAME COLUMN account_new TO account;
+
+  CREATE INDEX catalog_idx ON domains(catalog);
 COMMIT;
index 2f7598e93315480fcfe86d9ac72220b9781066e8..bbd39645e434ba69ba05ff3ce65d84dd25d37baa 100644 (file)
@@ -108,7 +108,7 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=$1 and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=$1 and name=$2 and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,account from domains where name=$1");
+    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=$1");
 
     declare(suffix, "info-all-slaves-query", "", "select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix, "supermaster-query", "", "select account from supermasters where ip=$1 and nameserver=$2");
@@ -135,6 +135,7 @@ public:
     declare(suffix, "update-master-query", "", "update domains set master=$1 where name=$2");
     declare(suffix, "update-kind-query", "", "update domains set type=$1 where name=$2");
     declare(suffix, "update-options-query", "", "update domains set options=$1 where name=$2");
+    declare(suffix, "update-catalog-query", "", "update domains set catalog=$1 where name=$2");
     declare(suffix, "update-account-query", "", "update domains set account=$1 where name=$2");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=$1 where id=$2");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=$1 where id=$2");
index 29bb1793b5fa13247b418397dece5e4fc1fbe2df..535328998b515eff0172cbfef5ef706a75fb1abe 100644 (file)
@@ -5,12 +5,14 @@ CREATE TABLE domains (
   last_check            INT DEFAULT NULL,
   type                  VARCHAR(6) NOT NULL,
   notified_serial       BIGINT DEFAULT NULL,
-  options               VARCHAR(65535) DEFAULT NULL;
+  options               VARCHAR(65535) DEFAULT NULL,
+  catalog               VARCHAR(255) DEFAULT NULL,
   account               VARCHAR(40) DEFAULT NULL,
   CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
 );
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
 
 
 CREATE TABLE records (
index 32374d5ca2a08a5a2f826aadf4c60a2653ec3dd2..b48071ff550c65ca18610a1027c23df5ff70b7e9 100644 (file)
@@ -9,14 +9,16 @@ BEGIN TRANSACTION;
     type                  VARCHAR(6) NOT NULL,
     notified_serial       INTEGER DEFAULT NULL,
     options               VARCHAR(65535) DEFAULT NULL,
+    catalog               VARCHAR(255) DEFAULT NULL,
     account               VARCHAR(40) DEFAULT NULL
   );
 
-  INSERT INTO domains_temp SELECT id,name,master,last_check,type,motified_serial,NULL,account FROM domains;
+  INSERT INTO domains_temp SELECT id,name,master,last_check,type,motified_serial,NULL,NULL,account FROM domains;
   DROP TABLE domains;
   ALTER TABLE domains_temp RENAME TO domains;
 
   CREATE UNIQUE INDEX name_index ON domains(name);
+  CREATE INDEX catalog_idx ON domains(catalog);
 COMMIT;
 
 PRAGMA foreign_keys = 1;
index 55d9f387ac21bbf7ec6def9cec23cb13e5c52a0f..a0ff8a0b8b15ed815139d70d0a1c3ca796ce7e40 100644 (file)
@@ -94,7 +94,7 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=:domain_id and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=:domain_id and name=:qname and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,account from domains where name=:domain");
+    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=:domain");
 
     declare(suffix, "info-all-slaves-query", "", "select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix, "supermaster-query", "", "select account from supermasters where ip=:ip and nameserver=:nameserver");
@@ -121,6 +121,7 @@ public:
     declare(suffix, "update-master-query", "", "update domains set master=:master where name=:domain");
     declare(suffix, "update-kind-query", "", "update domains set type=:kind where name=:domain");
     declare(suffix, "update-options-query", "", "update domains set options=:options where name=:domain");
+    declare(suffix, "update-catalog-query", "", "update domains set catalog=:options where name=:domain");
     declare(suffix, "update-account-query", "", "update domains set account=:account where name=:domain");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=:serial where id=:domain_id");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=:last_check where id=:domain_id");
index 506732075554dbaabacd7bab6723d1789af3442d..d1663b28a97a9860893f675234a7a263f7ebd14e 100644 (file)
@@ -7,11 +7,13 @@ CREATE TABLE domains (
   last_check            INTEGER DEFAULT NULL,
   type                  VARCHAR(6) NOT NULL,
   options               VARCHAR(65535) DEFAULT NULL,
+  catalog               VARCHAR(255) DEFAULT NULL,
   notified_serial       INTEGER DEFAULT NULL,
   account               VARCHAR(40) DEFAULT NULL
 );
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
 
 
 CREATE TABLE records (
index ae620a295e4ed92a45de5775688b26ec1c4edd5c..018faf820f07f56f9a550eb1846d802304a79326 100644 (file)
@@ -202,6 +202,7 @@ namespace serialization
     ar& g.notified_serial;
     ar& g.kind;
     ar& g.options;
+    ar& g.catalog;
   }
 
   template <class Archive>
@@ -216,9 +217,11 @@ namespace serialization
     ar& g.kind;
     if (version >= 1) {
       ar& g.options;
+      ar& g.catalog;
     }
     else {
       g.options.clear();
+      g.catalog.clear();
     }
   }
 
@@ -981,6 +984,13 @@ bool LMDBBackend::setOptions(const DNSName& domain, const std::string& options)
   });
 }
 
+bool LMDBBackend::setCatalog(const DNSName& domain, const DNSName& catalog)
+{
+  return genChangeDomain(domain, [catalog](DomainInfo& di) {
+    di.catalog = catalog;
+  });
+}
+
 bool LMDBBackend::setAccount(const DNSName& domain, const std::string& account)
 {
   return genChangeDomain(domain, [account](DomainInfo& di) {
index 70f9e54922d2813220d86b9a33ffc3ac2e6a4148..34a4eb95ac54a9416e320656ee3ed442ce06e34f 100644 (file)
@@ -107,6 +107,7 @@ public:
   void setFresh(uint32_t domain_id) override;
   void setNotified(uint32_t id, uint32_t serial) override;
   bool setOptions(const DNSName& domain, const std::string& options) override;
+  bool setCatalog(const DNSName& domain, const DNSName& options) override;
   bool setAccount(const DNSName& domain, const std::string& account) override;
   bool deleteDomain(const DNSName& domain) override;
 
index 0f4241c733dc714256c0bbc65663fac4c37862c4..bf15ea450fa718c3c9af33038e6b84ce7475a14f 100644 (file)
@@ -80,6 +80,7 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   d_UpdateSerialOfZoneQuery=getArg("update-serial-query");
   d_UpdateLastCheckOfZoneQuery=getArg("update-lastcheck-query");
   d_UpdateOptionsOfZoneQuery = getArg("update-options-query");
+  d_UpdateCatalogOfZoneQuery = getArg("update-catalog-query");
   d_UpdateAccountOfZoneQuery=getArg("update-account-query");
   d_InfoOfAllMasterDomainsQuery=getArg("info-all-master-query");
   d_DeleteDomainQuery=getArg("delete-domain-query");
@@ -154,6 +155,7 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   d_UpdateSerialOfZoneQuery_stmt = nullptr;
   d_UpdateLastCheckOfZoneQuery_stmt = nullptr;
   d_UpdateOptionsOfZoneQuery_stmt = nullptr;
+  d_UpdateCatalogOfZoneQuery_stmt = nullptr;
   d_UpdateAccountOfZoneQuery_stmt = nullptr;
   d_InfoOfAllMasterDomainsQuery_stmt = nullptr;
   d_DeleteDomainQuery_stmt = nullptr;
@@ -296,6 +298,25 @@ bool GSQLBackend::setOptions(const DNSName& domain, const string& options)
   return true;
 }
 
+bool GSQLBackend::setCatalog(const DNSName& domain, const DNSName& catalog)
+{
+  try {
+    reconnectIfNeeded();
+
+    // clang-format off
+    d_UpdateCatalogOfZoneQuery_stmt->
+      bind("catalog", catalog)->
+      bind("domain", domain)->
+      execute()->
+      reset();
+    // clang-format on
+  }
+  catch (SSqlException& e) {
+    throw PDNSException("GSQLBackend unable to set catalog of domain '" + domain.toLogString() + "' to '" + catalog.toLogString() + "': " + e.txtReason());
+  }
+  return true;
+}
+
 bool GSQLBackend::setAccount(const DNSName &domain, const string &account)
 {
   try {
@@ -334,17 +355,18 @@ bool GSQLBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getS
   if(!numanswers)
     return false;
 
-  ASSERT_ROW_COLUMNS("info-zone-query", d_result[0], 8);
+  ASSERT_ROW_COLUMNS("info-zone-query", d_result[0], 9);
 
   pdns::checked_stoi_into(di.id, d_result[0][0]);
   try {
     di.zone=DNSName(d_result[0][1]);
+    di.catalog = (!d_result[0][7].empty() ? DNSName(d_result[0][7]) : DNSName());
   } catch (...) {
     return false;
   }
   string type=d_result[0][5];
   di.options = d_result[0][6];
-  di.account = d_result[0][7];
+  di.account = d_result[0][8];
   di.kind = DomainInfo::stringToKind(type);
 
   vector<string> masters;
index dbe02aac6105a250cd911a1b221b069730626865..e7ffdb3370e5fa078f5299dfe3923255b3ffbb91 100644 (file)
@@ -75,6 +75,7 @@ protected:
       d_UpdateMasterOfZoneQuery_stmt = d_db->prepare(d_UpdateMasterOfZoneQuery, 2);
       d_UpdateKindOfZoneQuery_stmt = d_db->prepare(d_UpdateKindOfZoneQuery, 2);
       d_UpdateOptionsOfZoneQuery_stmt = d_db->prepare(d_UpdateOptionsOfZoneQuery, 2);
+      d_UpdateCatalogOfZoneQuery_stmt = d_db->prepare(d_UpdateCatalogOfZoneQuery, 2);
       d_UpdateAccountOfZoneQuery_stmt = d_db->prepare(d_UpdateAccountOfZoneQuery, 2);
       d_UpdateSerialOfZoneQuery_stmt = d_db->prepare(d_UpdateSerialOfZoneQuery, 2);
       d_UpdateLastCheckOfZoneQuery_stmt = d_db->prepare(d_UpdateLastCheckOfZoneQuery, 2);
@@ -142,6 +143,7 @@ protected:
     d_UpdateMasterOfZoneQuery_stmt.reset();
     d_UpdateKindOfZoneQuery_stmt.reset();
     d_UpdateOptionsOfZoneQuery_stmt.reset();
+    d_UpdateCatalogOfZoneQuery_stmt.reset();
     d_UpdateAccountOfZoneQuery_stmt.reset();
     d_UpdateSerialOfZoneQuery_stmt.reset();
     d_UpdateLastCheckOfZoneQuery_stmt.reset();
@@ -215,6 +217,7 @@ public:
   bool setMasters(const DNSName &domain, const vector<ComboAddress> &masters) override;
   bool setKind(const DNSName &domain, const DomainInfo::DomainKind kind) override;
   bool setOptions(const DNSName& domain, const string& options) override;
+  bool setCatalog(const DNSName& domain, const DNSName& catalog) override;
   bool setAccount(const DNSName &domain, const string &account) override;
 
   bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after) override;
@@ -307,6 +310,7 @@ private:
   string d_UpdateMasterOfZoneQuery;
   string d_UpdateKindOfZoneQuery;
   string d_UpdateOptionsOfZoneQuery;
+  string d_UpdateCatalogOfZoneQuery;
   string d_UpdateAccountOfZoneQuery;
   string d_UpdateSerialOfZoneQuery;
   string d_UpdateLastCheckOfZoneQuery;
@@ -381,6 +385,7 @@ private:
   unique_ptr<SSqlStatement> d_UpdateMasterOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateKindOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateOptionsOfZoneQuery_stmt;
+  unique_ptr<SSqlStatement> d_UpdateCatalogOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateAccountOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateSerialOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateLastCheckOfZoneQuery_stmt;
index 7007d5fe9042835c1d006d11b9d006e1c4e9383d..7f2fd40c9d89745cebc60a19080173eae5d15e94 100644 (file)
@@ -57,7 +57,10 @@ public:
   virtual SSqlStatement* bind(const string& name, unsigned long long value)=0;
   virtual SSqlStatement* bind(const string& name, const std::string& value)=0;
   SSqlStatement* bind(const string& name, const DNSName& value) {
-    return bind(name, value.makeLowerCase().toStringRootDot());
+    if (!value.empty()) {
+      return bind(name, value.makeLowerCase().toStringRootDot());
+    }
+    return bind(name, string(""));
   }
   virtual SSqlStatement* bindNull(const string& name)=0;
   virtual SSqlStatement* execute()=0;;
index a73d4438df2bf1f3de1c3cd3cecc717c8d340fd5..e48c0ca5ace8ef705de7eaa7f5f38d0bf1394161 100644 (file)
@@ -49,6 +49,7 @@ struct DomainInfo
   DomainInfo() : last_check(0), backend(nullptr), id(0), notified_serial(0), receivedNotify(false), serial(0), kind(DomainInfo::Native) {}
 
   DNSName zone;
+  DNSName catalog;
   time_t last_check;
   string options;
   string account;
@@ -356,6 +357,12 @@ public:
     return false;
   }
 
+  //! Called when the catalog of a domain should be changed
+  virtual bool setCatalog(const DNSName& domain, const DNSName& catalog)
+  {
+    return false;
+  }
+
   //! Called when the Account of a domain should be changed
   virtual bool setAccount(const DNSName &domain, const string &account)
   {
index 75826d177434f0b85ae64aba7ccf956e6c8d2022..ad033150a2a7ea24d2a7e7b520d6b588bcb5a142 100644 (file)
@@ -1875,6 +1875,22 @@ static int setZoneOptions(const DNSName& zone, const string& options)
   return EXIT_SUCCESS;
 }
 
+static int setZoneCatalog(const DNSName& zone, const DNSName& catalog)
+{
+  UeberBackend B("default");
+  DomainInfo di;
+
+  if (!B.getDomainInfo(zone, di)) {
+    cerr << "No such zone " << zone << " in the database" << endl;
+    return EXIT_FAILURE;
+  }
+  if (!di.backend->setCatalog(zone, catalog)) {
+    cerr << "Could not find backend willing to accept new zone configuration" << endl;
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}
+
 static int setZoneAccount(const DNSName& zone, const string &account)
 {
   UeberBackend B("default");
@@ -2122,6 +2138,10 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
     cout << "Options:" << endl;
     cout << di.options << endl;
   }
+  if (!di.catalog.empty()) {
+    cout << "Catalog: " << endl;
+    cout << di.catalog << endl;
+  }
   return true;
 }
 
@@ -2486,6 +2506,7 @@ try
     cout << "secure-zone ZONE [ZONE ..]         Add DNSSEC to zone ZONE" << endl;
     cout << "set-kind ZONE KIND                 Change the kind of ZONE to KIND (primary, secondary, native)" << endl;
     cout << "set-options ZONE OPTIONS           Change the options of ZONE to OPTIONS" << endl;
+    cout << "set-catalog ZONE CATALOG           Change the catalog of ZONE to CATALOG" << endl;
     cout << "set-account ZONE ACCOUNT           Change the account (owner) of ZONE to ACCOUNT" << endl;
     cout << "set-nsec3 ZONE ['PARAMS' [narrow]] Enable NSEC3 with PARAMS. Optionally narrow" << endl;
     cout << "set-presigned ZONE                 Use presigned RRSIGs from storage" << endl;
@@ -3138,15 +3159,29 @@ try
       return 0;
     }
     // Verify json
-    std::string err;
-    json11::Json doc = json11::Json::parse(cmds.at(2), err);
-    if (doc.is_null()) {
-      cerr << "Parsing of JSON document failed:" << err << endl;
-      return EXIT_FAILURE;
+    if (!cmds.at(2).empty()) {
+      std::string err;
+      json11::Json doc = json11::Json::parse(cmds.at(2), err);
+      if (doc.is_null()) {
+        cerr << "Parsing of JSON document failed:" << err << endl;
+        return EXIT_FAILURE;
+      }
     }
     DNSName zone(cmds.at(1));
     return setZoneOptions(zone, cmds.at(2));
   }
+  else if (cmds.at(0) == "set-catalog") {
+    if (cmds.size() != 3) {
+      cerr << "Syntax: pdnsutil set-catalog ZONE CATALOG" << endl;
+      return 0;
+    }
+    DNSName zone(cmds.at(1));
+    DNSName catalog; // Create an empty DNSName()
+    if (!cmds.at(2).empty()) {
+      catalog = DNSName(cmds.at(2));
+    }
+    return setZoneCatalog(zone, catalog);
+  }
   else if (cmds.at(0) == "set-account") {
     if(cmds.size() != 3) {
       cerr<<"Syntax: pdnsutil set-account ZONE ACCOUNT"<<endl;