]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Split main() into one routine per command.
authorMiod Vallat <miod.vallat@open-xchange.com>
Wed, 15 Jan 2025 07:48:17 +0000 (08:48 +0100)
committerMiod Vallat <miod.vallat@open-xchange.com>
Wed, 15 Jan 2025 08:01:01 +0000 (09:01 +0100)
This unfortunately removes the "hash-password" easter egg.

pdns/pdnsutil.cc

index 866a442d534bc52c3df267230b92f8d8bf654a22..205fdc7f46b1be10f307443c60fee0656d10412f 100644 (file)
@@ -1545,52 +1545,6 @@ static int createZone(const DNSName &zone, const DNSName& nsname) {
   return EXIT_SUCCESS;
 }
 
-static int createSecondaryZone(const vector<string>& cmds)
-{
-  UeberBackend B;
-  DomainInfo di;
-  DNSName zone(cmds.at(1));
-  if (B.getDomainInfo(zone, di)) {
-    cerr << "Zone '" << zone << "' exists already" << endl;
-    return EXIT_FAILURE;
-  }
-  vector<ComboAddress> primaries;
-  for (unsigned i=2; i < cmds.size(); i++) {
-    primaries.emplace_back(cmds.at(i), 53);
-  }
-  cerr << "Creating secondary zone '" << zone << "', with primaries '" << comboAddressVecToString(primaries) << "'" << endl;
-  B.createDomain(zone, DomainInfo::Secondary, primaries, "");
-  if(!B.getDomainInfo(zone, di)) {
-    cerr << "Zone '" << zone << "' was not created!" << endl;
-    return EXIT_FAILURE;
-  }
-  return EXIT_SUCCESS;
-}
-
-static int changeSecondaryZonePrimary(const vector<string>& cmds)
-{
-  UeberBackend B;
-  DomainInfo di;
-  DNSName zone(cmds.at(1));
-  if (!B.getDomainInfo(zone, di)) {
-    cerr << "Zone '" << zone << "' doesn't exist" << endl;
-    return EXIT_FAILURE;
-  }
-  vector<ComboAddress> primaries;
-  for (unsigned i=2; i < cmds.size(); i++) {
-    primaries.emplace_back(cmds.at(i), 53);
-  }
-  cerr << "Updating secondary zone '" << zone << "', primaries to '" << comboAddressVecToString(primaries) << "'" << endl;
-  try {
-    di.backend->setPrimaries(zone, primaries);
-    return EXIT_SUCCESS;
-  }
-  catch (PDNSException& e) {
-    cerr << "Setting primary for zone '" << zone << "' failed: " << e.reason << endl;
-    return EXIT_FAILURE;
-  }
-}
-
 // add-record ZONE name type [ttl] "content" ["content"]
 static int addOrReplaceRecord(bool addOrReplace, const vector<string>& cmds) {
   DNSResourceRecord rr;
@@ -2515,1794 +2469,2271 @@ static int addOrSetMeta(const DNSName& zone, const string& kind, const vector<st
   return 0;
 }
 
-static int addZoneKey(vector<string>& cmds, DNSSECKeeper& dk) //NOLINT(readability-identifier-length)
+// Command handlers
+
+static int lmdbGetBackendVersion([[maybe_unused]] vector<string>& cmds)
 {
-  if(cmds.size() < 3 ) {
-    cerr << "Syntax: pdnsutil add-zone-key ZONE [zsk|ksk] [BITS] [active|inactive] [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
-#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO_ED25519)
-    cerr << "|ed25519";
-#endif
-#if defined(HAVE_LIBCRYPTO_ED448)
-    cerr << "|ed448";
-#endif
-    cerr << "]"<<endl;
-    cerr << endl;
-    cerr << "If zsk|ksk is omitted, add-zone-key makes a key with flags 256 (a 'ZSK')."<<endl;
+  cout << "5" << endl; // FIXME this should reuse the constant from lmdbbackend but that is currently a #define in a .cc
+  return 0;
+}
+
+static int testAlgorithm(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil test-algorithm algonum"<<endl;
     return 0;
   }
-  DNSName zone(cmds.at(1));
+  if (testAlgorithm(pdns::checked_stoi<int>(cmds.at(1))))
+    return 0;
+  return 1;
+}
 
-  UeberBackend B("default"); //NOLINT(readability-identifier-length)
-  DomainInfo di; //NOLINT(readability-identifier-length)
+static int ipEncrypt(vector<string>& cmds)
+{
+  if (cmds.size() < 3 || (cmds.size() == 4 && cmds.at(3) != "key")) {
+    cerr<<"Syntax: pdnsutil [ipencrypt|ipdecrypt] IP passphrase [key]"<<endl;
+    return 0;
+  }
+#ifdef HAVE_IPCIPHER
+  string key;
+  if(cmds.size()==4) {
+    if (B64Decode(cmds.at(2), key) < 0) {
+      cerr << "Could not parse '" << cmds.at(3) << "' as base64" << endl;
+      return 0;
+    }
+  }
+  else {
+    key = makeIPCipherKey(cmds.at(2));
+  }
+  return xcryptIP(cmds.at(0), cmds.at(1), key);
+#else
+  cerr<<cmds.at(0)<<" requires ipcipher support which is not available"<<endl;
+  return 0;
+#endif /* HAVE_IPCIPHER */
+}
 
-  if (!B.getDomainInfo(zone, di)){
-    cerr << "No such zone in the database" << endl;
+static int testAlgorithms([[maybe_unused]] vector<string>& cmds)
+{
+  if (testAlgorithms())
     return 0;
+  return 1;
+}
+
+static int listAlgorithms(vector<string>& cmds)
+{
+  if ((cmds.size() == 2 && cmds.at(1) != "with-backend") || cmds.size() > 2) {
+    cerr<<"Syntax: pdnsutil list-algorithms [with-backend]"<<endl;
+    return 1;
   }
 
-  // need to get algorithm, bits & ksk or zsk from commandline
-  bool keyOrZone=false;
-  int tmp_algo=0;
-  int bits=0;
-  int algorithm=DNSSECKeeper::ECDSA256;
-  bool active=false;
-  bool published=true;
-  for(unsigned int n=2; n < cmds.size(); ++n) { //NOLINT(readability-identifier-length)
-    if (pdns_iequals(cmds.at(n), "zsk")) {
-      keyOrZone = false;
-    }
-    else if (pdns_iequals(cmds.at(n), "ksk")) {
-      keyOrZone = true;
-    }
-    else if ((tmp_algo = DNSSECKeeper::shorthand2algorithm(cmds.at(n))) > 0) {
-      algorithm = tmp_algo;
-    }
-    else if (pdns_iequals(cmds.at(n), "active")) {
-      active=true;
-    }
-    else if (pdns_iequals(cmds.at(n), "inactive") || pdns_iequals(cmds.at(n), "passive")) { // 'passive' eventually needs to be removed
-      active=false;
-    }
-    else if (pdns_iequals(cmds.at(n), "published")) {
-      published = true;
-    }
-    else if (pdns_iequals(cmds.at(n), "unpublished")) {
-      published = false;
+  cout<<"DNSKEY algorithms supported by this installation of PowerDNS:"<<endl;
+
+  auto algosWithBackend = DNSCryptoKeyEngine::listAllAlgosWithBackend();
+  for (const auto& algoWithBackend : algosWithBackend){
+    string algoName = DNSSECKeeper::algorithm2name(algoWithBackend.first);
+    cout<<std::to_string(algoWithBackend.first)<<" - "<<algoName;
+    if (cmds.size() == 2 && cmds.at(1) == "with-backend")
+      cout<<" using "<<algoWithBackend.second;
+    cout<<endl;
+  }
+  return 0;
+}
+
+
+// these need reportAllTypes
+static int createBindDb(vector<string>& cmds)
+{
+#ifdef HAVE_SQLITE3
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil create-bind-db FNAME"<<endl;
+    return 0;
+  }
+  try {
+    SSQLite3 db(cmds.at(1), "", true); // create=ok
+    vector<string> statements;
+    stringtok(statements, sqlCreate, ";");
+    for(const string& statement :  statements) {
+      db.execute(statement);
     }
-    else if (pdns::checked_stoi<int>(cmds.at(n)) != 0) {
-      pdns::checked_stoi_into(bits, cmds.at(n));
+  }
+  catch(SSqlException& se) {
+    throw PDNSException("Error creating database in BIND backend: "+se.txtReason());
+  }
+  return 0;
+#else
+  cerr<<"bind-dnssec-db requires building PowerDNS with SQLite3"<<endl;
+  return 1;
+#endif
+}
+
+static int rawLuaFromContent(vector<string>& cmds)
+{
+  if (cmds.size() < 3) {
+    cerr<<"Usage: raw-lua-from-content TYPE CONTENT"<<endl;
+    return 1;
+  }
+
+  // DNSResourceRecord rr;
+  // rr.qtype = DNSRecordContent::TypeToNumber(cmds.at(1));
+  // rr.content = cmds.at(2);
+  auto drc = DNSRecordContent::make(DNSRecordContent::TypeToNumber(cmds.at(1)), QClass::IN, cmds.at(2));
+  cout<<makeLuaString(drc->serialize(DNSName(), true))<<endl;
+
+  return 0;
+}
+
+static int hashPassword(vector<string>& cmds)
+{
+  uint64_t workFactor = CredentialsHolder::s_defaultWorkFactor;
+  if (cmds.size() > 1) {
+    try {
+      pdns::checked_stoi_into(workFactor, cmds.at(1));
     }
-    else {
-      cerr << "Unknown algorithm, key flag or size '" << cmds.at(n) << "'" << endl;
-      return EXIT_FAILURE;
+    catch (const std::exception& e) {
+      cerr<<"Unable to parse the supplied work factor: "<<e.what()<<endl;
+      return 1;
     }
   }
-  int64_t id{-1}; //NOLINT(readability-identifier-length)
-  if (!dk.addKey(zone, keyOrZone, algorithm, id, bits, active, published)) {
-    cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
+
+  auto password = CredentialsHolder::readFromTerminal();
+
+  try {
+    cout<<hashPassword(password.getString(), workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize)<<endl;
+    return EXIT_SUCCESS;
+  }
+  catch (const std::exception& e) {
+    cerr<<"Error while hashing the supplied password: "<<e.what()<<endl;
     return 1;
   }
-  cerr<<"Added a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<", active="<<active<<endl;
-  if (bits != 0) {
-    cerr<<"Requested specific key size of "<<bits<<" bits"<<endl;
+}
+
+static int zonemdVerifyFile(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr<<"Syntax: pdnsutil zonemd-verify-file ZONE FILENAME"<<endl;
+    return 1;
   }
-  if (id == -1) {
-    cerr<<std::to_string(id)<<": Key was added, but backend does not support returning of key id"<<endl;
-  } else if (id < -1) {
-    cerr<<std::to_string(id)<<": Key was added, but there was a failure while returning the key id"<<endl;
+  if(cmds[1]==".")
+    cmds[1].clear();
+
+  return zonemdVerifyFile(DNSName(cmds[1]), cmds[2]);
+}
+
+
+// these need DNSSECKeeper
+static int testSchema(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil test-schema ZONE"<<endl;
+    return 0;
+  }
+  DNSSECKeeper dk;
+  return testSchema(dk, DNSName(cmds.at(1)));
+}
+
+static int rectifyZone(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr << "Syntax: pdnsutil rectify-zone ZONE [ZONE..]"<<endl;
+    return 0;
+  }
+  DNSSECKeeper dk;
+  unsigned int exitCode = 0;
+  for(unsigned int n = 1; n < cmds.size(); ++n)
+    if (!rectifyZone(dk, DNSName(cmds.at(n))))
+      exitCode = 1;
+  return exitCode;
+}
+
+static int rectifyAllZones(vector<string>& cmds)
+{
+  bool quiet = (cmds.size() >= 2 && cmds.at(1) == "quiet");
+  DNSSECKeeper dk;
+  if (!rectifyAllZones(dk, quiet)) {
     return 1;
-  } else {
-    try {
-      dk.getKeyById(zone, id);
-      cout<<std::to_string(id)<<endl;
-    } catch (std::exception& e) {
-      cerr<<std::to_string(id)<<": Key was added, but there was a failure while reading it back: " <<e.what()<<endl;
-      return 1;
-    }
   }
   return 0;
 }
 
-// NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Clean this function up.
-int main(int argc, char** argv)
-try
+static int checkZone(vector<string>& cmds)
 {
-  po::options_description desc("Allowed options");
-  desc.add_options()
-    ("help,h", "produce help message")
-    ("version", "show version")
-    ("verbose,v", "be verbose")
-    ("force", "force an action")
-    ("config-name", po::value<string>()->default_value(""), "virtual configuration name")
-    ("config-dir", po::value<string>()->default_value(SYSCONFDIR), "location of pdns.conf")
-    ("no-colors", "do not use colors in output")
-    ("commands", po::value<vector<string> >());
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil check-zone ZONE"<<endl;
+    return 0;
+  }
+  DNSSECKeeper dk;
+  UeberBackend B("default");
+  return checkZone(dk, B, DNSName(cmds.at(1)));
+}
 
-  po::positional_options_description p;
-  p.add("commands", -1);
-  po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), g_vm);
-  po::notify(g_vm);
+static int benchDb(vector<string>& cmds)
+{
+  dbBench(cmds.size() > 1 ? cmds.at(1) : "");
+  return 0;
+}
 
-  vector<string> cmds;
+static int checkAllZones(vector<string>& cmds)
+{
+  bool exitOnError = ((cmds.size() >= 2 ? cmds.at(1) : "") == "exit-on-error");
+  DNSSECKeeper dk;
+  return checkAllZones(dk, exitOnError);
+}
 
-  if(g_vm.count("commands"))
-    cmds = g_vm["commands"].as<vector<string> >();
+static int listAllZones(vector<string>& cmds)
+{
+  if (cmds.size() > 2) {
+    cerr << "Syntax: pdnsutil list-all-zones [primary|secondary|native|producer|consumer]" << endl;
+    return 0;
+  }
+  if (cmds.size() == 2)
+    return listAllZones(cmds.at(1));
+  return listAllZones();
+}
 
-  g_verbose = g_vm.count("verbose");
+static int listMemberZones(vector<string>& cmds)
+{
+  if (cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil list-member-zones CATALOG" << endl;
+    return 0;
+  }
+  return listMemberZones(cmds.at(1));
+}
 
-  if (g_vm.count("version")) {
-    cout<<"pdnsutil "<<VERSION<<endl;
+static int testZone([[maybe_unused]] vector<string>& cmds)
+{
+  cerr << "Did you mean check-zone?"<<endl;
+  return 0;
+}
+
+static int testAllZones([[maybe_unused]] vector<string>& cmds)
+{
+  cerr << "Did you mean check-all-zones?"<<endl;
+  return 0;
+}
+
+static int testSpeed(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr << "Syntax: pdnsutil test-speed numcores [signing-server]"<<endl;
     return 0;
   }
+  testSpeed(DNSName(cmds.at(1)), (cmds.size() > 3) ? cmds.at(3) : "", pdns::checked_stoi<int>(cmds.at(2)));
+  return 0;
+}
 
-  if (cmds.empty() || g_vm.count("help") || cmds.at(0) == "help") {
-    cout << "Usage: \npdnsutil [options] <command> [params ..]\n"
-         << endl;
-    cout << "Commands:" << endl;
-    cout << "activate-tsig-key ZONE NAME {primary|secondary|producer|consumer}" << endl;
-    cout << "                                   Enable TSIG authenticated AXFR using the key NAME for ZONE" << endl;
-    cout << "activate-zone-key ZONE KEY-ID      Activate the key with key id KEY-ID in ZONE" << endl;
-    cout << "add-record ZONE NAME TYPE [ttl] content" << endl;
-    cout << "             [content..]           Add one or more records to ZONE" << endl;
-    cout << "add-autoprimary IP NAMESERVER [account]" << endl;
-    cout << "                                   Add a new autoprimary " << endl;
-    cout << "remove-autoprimary IP NAMESERVER   Remove an autoprimary" << endl;
-    cout << "list-autoprimaries                 List all autoprimaries" << endl;
-    cout << "add-zone-key ZONE {zsk|ksk} [BITS] [active|inactive] [published|unpublished]" << endl;
-    cout << "             [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
-#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO_ED25519)
-    cout << "|ed25519";
-#endif
-#if defined(HAVE_LIBCRYPTO_ED448)
-    cout << "|ed448";
-#endif
-    cout << "]" << endl;
-    cout << "                                   Add a ZSK or KSK to zone and specify algo&bits" << endl;
-    cout << "backend-cmd BACKEND CMD [CMD..]    Perform one or more backend commands" << endl;
-    cout << "backend-lookup BACKEND NAME [[TYPE] CLIENT-IP-SUBNET]" << endl;
-    cout << "                                   Perform a backend lookup of NAME, TYPE and CLIENT-IP-SUBNET" << endl;
-    cout << "b2b-migrate OLD NEW                Move all data from one backend to another" << endl;
-    cout << "bench-db [filename]                Bench database backend with queries, one zone per line" << endl;
-    cout << "check-zone ZONE                    Check a zone for correctness" << endl;
-    cout << "check-all-zones [exit-on-error]    Check all zones for correctness. Set exit-on-error to exit immediately" << endl;
-    cout << "                                   after finding an error in a zone." << endl;
-    cout << "clear-zone ZONE                    Clear all records of a zone, but keep everything else" << endl;
-    cout << "create-bind-db FNAME               Create DNSSEC db for BIND backend (bind-dnssec-db)" << endl;
-    cout << "create-secondary-zone ZONE primary-ip [primary-ip..]" << endl;
-    cout << "                                   Create secondary zone ZONE with primary IP address primary-ip" << endl;
-    cout << "change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl;
-    cout << "                                   Change secondary zone ZONE primary IP address to primary-ip" << endl;
-    cout << "create-zone ZONE [nsname]          Create empty zone ZONE" << endl;
-    cout << "deactivate-tsig-key ZONE NAME {primary|secondary}" << endl;
-    cout << "                                   Disable TSIG authenticated AXFR using the key NAME for ZONE" << endl;
-    cout << "deactivate-zone-key ZONE KEY-ID    Deactivate the key with key id KEY-ID in ZONE" << endl;
-    cout << "delete-rrset ZONE NAME TYPE        Delete named RRSET from zone" << endl;
-    cout << "delete-tsig-key NAME               Delete TSIG key (warning! will not unmap key!)" << endl;
-    cout << "delete-zone ZONE                   Delete the zone" << endl;
-    cout << "disable-dnssec ZONE                Deactivate all keys and unset PRESIGNED in ZONE" << endl;
-    cout << "edit-zone ZONE                     Edit zone contents using $EDITOR" << endl;
-    cout << "export-zone-dnskey ZONE KEY-ID     Export to stdout the public DNSKEY described" << endl;
-    cout << "export-zone-ds ZONE                Export to stdout all KSK DS records for ZONE" << endl;
-    cout << "export-zone-key ZONE KEY-ID        Export to stdout the private key described" << endl;
-    cout << "export-zone-key-pem ZONE KEY-ID    Export to stdout in PEM the private key described" << endl;
-    cout << "generate-tsig-key NAME ALGORITHM   Generate new TSIG key" << endl;
-    cout << "generate-zone-key {zsk|ksk} [ALGORITHM] [BITS]" << endl;
-    cout << "                                   Generate a ZSK or KSK to stdout with specified ALGORITHM and BITS" << endl;
-    cout << "get-meta ZONE [KIND ...]           Get zone metadata. If no KIND given, lists all known" << endl;
-    cout << "hash-password [WORK FACTOR]        Ask for a plaintext password or api key and output a hashed and salted version" << endl;
-    cout << "hash-zone-record ZONE RNAME        Calculate the NSEC3 hash for RNAME in ZONE" << endl;
-#ifdef HAVE_P11KIT1
-    cout << "hsm assign ZONE ALGORITHM {ksk|zsk} MODULE SLOT PIN LABEL" << endl <<
-            "                                   Assign a hardware signing module to a ZONE" << endl;
-    cout << "hsm create-key ZONE KEY-ID [BITS]  Create a key using hardware signing module for ZONE (use assign first)" << endl;
-    cout << "                                   BITS defaults to 2048" << endl;
-#endif
-    cout << "increase-serial ZONE               Increases the SOA-serial by 1. Uses SOA-EDIT" << endl;
-    cout << "import-tsig-key NAME ALGORITHM KEY Import TSIG key" << endl;
-    cout << "import-zone-key ZONE FILE          Import from a file a private key, ZSK or KSK" << endl;
-    cout << "       [active|inactive] [ksk|zsk] [published|unpublished] Defaults to KSK, active and published" << endl;
-    cout << "import-zone-key-pem ZONE FILE      Import a private key from a PEM file" << endl;
-    cout << "        ALGORITHM {ksk|zsk}" << endl;
-    cout << "ipdecrypt IP passphrase/key [key]  Decrypt IP address using passphrase or base64 key" << endl;
-    cout << "ipencrypt IP passphrase/key [key]  Encrypt IP address using passphrase or base64 key" << endl;
-    cout << "load-zone ZONE FILE                Load ZONE from FILE, possibly creating zone or atomically" << endl;
-    cout << "                                   replacing contents" << endl;
-    cout << "list-algorithms [with-backend]     List all DNSSEC algorithms supported, optionally also listing the crypto library used" << endl;
-    cout << "list-keys [ZONE]                   List DNSSEC keys for ZONE. When ZONE is unset, display all keys for all active zones" << endl;
-    cout << "                                   --verbose or -v will also include the keys for disabled or empty zones" << endl;
-    cout << "list-zone ZONE                     List zone contents" << endl;
-    cout << "list-all-zones [primary|secondary|native|producer|consumer]" << endl;
-    cout << "                                   List all active zone names. --verbose or -v will also include disabled or empty zones" << endl;
-    cout << "list-member-zones CATALOG          List all members of catalog zone CATALOG" << endl;
-
-    cout << "list-tsig-keys                     List all TSIG keys" << endl;
-    cout << "publish-zone-key ZONE KEY-ID       Publish the zone key with key id KEY-ID in ZONE" << endl;
-    cout << "rectify-zone ZONE [ZONE ..]        Fix up DNSSEC fields (order, auth)" << endl;
-    cout << "rectify-all-zones [quiet]          Rectify all zones. Optionally quiet output with errors only" << endl;
-    cout << "remove-zone-key ZONE KEY-ID        Remove key with KEY-ID from ZONE" << endl;
-    cout << "replace-rrset ZONE NAME TYPE [ttl] Replace named RRSET from zone" << endl;
-    cout << "       content [content..]" << endl;
-    cout << "secure-all-zones [increase-serial] Secure all zones without keys" << endl;
-    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, producer, consumer)" << endl;
-    cout << "set-options-json ZONE JSON         Change the options of ZONE to JSON" << endl;
-    cout << "set-option ZONE                    Set or remove an option for ZONE Providing an empty value removes an option" << endl;
-    cout << "  [producer|consumer]" << endl;
-    cout << "  [coo|unique|group] VALUE" << endl;
-    cout << "  [VALUE ...]" << endl;
-    cout << "set-catalog ZONE CATALOG           Change the catalog of ZONE to CATALOG. Setting CATALOG to an empty "" removes ZONE from the catalog it is in" << 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;
-    cout << "set-publish-cdnskey ZONE [delete]  Enable sending CDNSKEY responses for ZONE. Add 'delete' to publish a CDNSKEY with a" << endl;
-    cout << "                                   DNSSEC delete algorithm" << endl;
-    cout << "set-publish-cds ZONE [DIGESTALGOS] Enable sending CDS responses for ZONE, using DIGESTALGOS as signature algorithms" << endl;
-    cout << "                                   DIGESTALGOS should be a comma separated list of numbers, it is '2' by default" << endl;
-    cout << "add-meta ZONE KIND VALUE           Add zone metadata, this adds to the existing KIND" << endl;
-    cout << "                   [VALUE ...]" << endl;
-    cout << "set-meta ZONE KIND [VALUE] [VALUE] Set zone metadata, optionally providing a value. *No* value clears meta" << endl;
-    cout << "                                   Note - this will replace all metadata records of KIND!" << endl;
-    cout << "show-zone ZONE                     Show DNSSEC (public) key details about a zone" << endl;
-    cout << "unpublish-zone-key ZONE KEY-ID     Unpublish the zone key with key id KEY-ID in ZONE" << endl;
-    cout << "unset-nsec3 ZONE                   Switch back to NSEC" << endl;
-    cout << "unset-presigned ZONE               No longer use presigned RRSIGs" << endl;
-    cout << "unset-publish-cdnskey ZONE         Disable sending CDNSKEY responses for ZONE" << endl;
-    cout << "unset-publish-cds ZONE             Disable sending CDS responses for ZONE" << endl;
-    cout << "test-schema ZONE                   Test DB schema - will create ZONE" << endl;
-    cout << "raw-lua-from-content TYPE CONTENT  Display record contents in a form suitable for dnsdist's `SpoofRawAction`" << endl;
-    cout << "zonemd-verify-file ZONE FILE       Validate ZONEMD for ZONE" << endl;
-    cout << "lmdb-get-backend-version           Get schema version supported by backend" << endl;
-    cout << desc << endl;
-
+static int verifyCrypto(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil verify-crypto FILE"<<endl;
     return 0;
   }
+  verifyCrypto(cmds.at(1));
+  return 0;
+}
 
-  loadMainConfig(g_vm["config-dir"].as<string>());
-
-  if (cmds.at(0) == "lmdb-get-backend-version") {
-    cout << "5" << endl; // FIXME this should reuse the constant from lmdbbackend but that is currently a #define in a .cc
+static int showZone(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil show-zone ZONE"<<endl;
     return 0;
   }
-  if (cmds.at(0) == "test-algorithm") {
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil test-algorithm algonum"<<endl;
-      return 0;
-    }
-    if (testAlgorithm(pdns::checked_stoi<int>(cmds.at(1))))
-      return 0;
+  DNSSECKeeper dk;
+  if (!showZone(dk, DNSName(cmds.at(1))))
     return 1;
-  }
+  return 0;
+}
 
-  if (cmds.at(0) == "ipencrypt" || cmds.at(0) == "ipdecrypt") {
-    if (cmds.size() < 3 || (cmds.size() == 4 && cmds.at(3) != "key")) {
-      cerr<<"Syntax: pdnsutil [ipencrypt|ipdecrypt] IP passphrase [key]"<<endl;
-      return 0;
-    }
-#ifdef HAVE_IPCIPHER
-    string key;
-    if(cmds.size()==4) {
-      if (B64Decode(cmds.at(2), key) < 0) {
-        cerr << "Could not parse '" << cmds.at(3) << "' as base64" << endl;
-        return 0;
-      }
-    }
-    else {
-      key = makeIPCipherKey(cmds.at(2));
-    }
-    exit(xcryptIP(cmds.at(0), cmds.at(1), key));
-#else
-    cerr<<cmds.at(0)<<" requires ipcipher support which is not available"<<endl;
+static int exportZoneDS(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil export-zone-ds ZONE"<<endl;
     return 0;
-#endif /* HAVE_IPCIPHER */
   }
-
-  if (cmds.at(0) == "test-algorithms") {
-    if (testAlgorithms())
-      return 0;
+  DNSSECKeeper dk;
+  if (!showZone(dk, DNSName(cmds.at(1)), true))
     return 1;
-  }
-
-  if (cmds.at(0) == "list-algorithms") {
-    if ((cmds.size() == 2 && cmds.at(1) != "with-backend") || cmds.size() > 2) {
-      cerr<<"Syntax: pdnsutil list-algorithms [with-backend]"<<endl;
-      return 1;
-    }
-
-    cout<<"DNSKEY algorithms supported by this installation of PowerDNS:"<<endl;
+  return 0;
+}
 
-    auto algosWithBackend = DNSCryptoKeyEngine::listAllAlgosWithBackend();
-    for (const auto& algoWithBackend : algosWithBackend){
-      string algoName = DNSSECKeeper::algorithm2name(algoWithBackend.first);
-      cout<<std::to_string(algoWithBackend.first)<<" - "<<algoName;
-      if (cmds.size() == 2 && cmds.at(1) == "with-backend")
-        cout<<" using "<<algoWithBackend.second;
-      cout<<endl;
-    }
+static int disableDNSSEC(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr << "Syntax: pdnsutil disable-dnssec ZONE"<<endl;
     return 0;
   }
-
-  reportAllTypes();
-
-  if (cmds.at(0) == "create-bind-db") {
-#ifdef HAVE_SQLITE3
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil create-bind-db FNAME"<<endl;
-      return 0;
-    }
-    try {
-      SSQLite3 db(cmds.at(1), "", true); // create=ok
-      vector<string> statements;
-      stringtok(statements, sqlCreate, ";");
-      for(const string& statement :  statements) {
-        db.execute(statement);
-      }
-    }
-    catch(SSqlException& se) {
-      throw PDNSException("Error creating database in BIND backend: "+se.txtReason());
-    }
-    return 0;
-#else
-    cerr<<"bind-dnssec-db requires building PowerDNS with SQLite3"<<endl;
+  DNSSECKeeper dk;
+  DNSName zone(cmds.at(1));
+  if(!disableDNSSECOnZone(dk, zone)) {
+    cerr << "Cannot disable DNSSEC on " << zone << endl;
     return 1;
-#endif
   }
+  return 0;
+}
 
-  if (cmds.at(0) == "raw-lua-from-content") {
-    if (cmds.size() < 3) {
-      cerr<<"Usage: raw-lua-from-content TYPE CONTENT"<<endl;
-      return 1;
-    }
-
-    // DNSResourceRecord rr;
-    // rr.qtype = DNSRecordContent::TypeToNumber(cmds.at(1));
-    // rr.content = cmds.at(2);
-    auto drc = DNSRecordContent::make(DNSRecordContent::TypeToNumber(cmds.at(1)), QClass::IN, cmds.at(2));
-    cout<<makeLuaString(drc->serialize(DNSName(), true))<<endl;
-
+static int activateZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() != 3) {
+    cerr << "Syntax: pdnsutil activate-zone-key ZONE KEY-ID"<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "hash-password") {
-    uint64_t workFactor = CredentialsHolder::s_defaultWorkFactor;
-    if (cmds.size() > 1) {
-      try {
-        pdns::checked_stoi_into(workFactor, cmds.at(1));
-      }
-      catch (const std::exception& e) {
-        cerr<<"Unable to parse the supplied work factor: "<<e.what()<<endl;
-        return 1;
-      }
-    }
-
-    auto password = CredentialsHolder::readFromTerminal();
-
-    try {
-      cout<<hashPassword(password.getString(), workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize)<<endl;
-      return EXIT_SUCCESS;
-    }
-    catch (const std::exception& e) {
-      cerr<<"Error while hashing the supplied password: "<<e.what()<<endl;
-      return 1;
-    }
-  }
-
-  if(cmds[0] == "zonemd-verify-file") {
-    if(cmds.size() < 3) {
-      cerr<<"Syntax: pdnsutil zonemd-verify-file ZONE FILENAME"<<endl;
-      return 1;
-    }
-    if(cmds[1]==".")
-      cmds[1].clear();
-
-    auto ret = zonemdVerifyFile(DNSName(cmds[1]), cmds[2]);
-    return ret;
+  DNSName zone(cmds.at(1));
+  unsigned int id = atoi(cmds.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
+  if(!id)
+  {
+    cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
+    return 1;
   }
-
   DNSSECKeeper dk;
-
-  if (cmds.at(0) == "test-schema") {
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil test-schema ZONE"<<endl;
-      return 0;
-    }
-    return testSchema(dk, DNSName(cmds.at(1)));
-  }
-  if (cmds.at(0) == "rectify-zone") {
-    if(cmds.size() < 2) {
-      cerr << "Syntax: pdnsutil rectify-zone ZONE [ZONE..]"<<endl;
-      return 0;
-    }
-    unsigned int exitCode = 0;
-    for(unsigned int n = 1; n < cmds.size(); ++n)
-      if (!rectifyZone(dk, DNSName(cmds.at(n))))
-        exitCode = 1;
-    return exitCode;
-  }
-  else if (cmds.at(0) == "rectify-all-zones") {
-    bool quiet = (cmds.size() >= 2 && cmds.at(1) == "quiet");
-    if (!rectifyAllZones(dk, quiet)) {
-      return 1;
-    }
-  }
-  else if (cmds.at(0) == "check-zone") {
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil check-zone ZONE"<<endl;
-      return 0;
-    }
-    UeberBackend B("default");
-    return checkZone(dk, B, DNSName(cmds.at(1)));
+  try {
+    dk.getKeyById(zone, id);
+  } catch (std::exception& e) {
+    cerr<<e.what()<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "bench-db") {
-    dbBench(cmds.size() > 1 ? cmds.at(1) : "");
+  if (!dk.activateKey(zone, id)) {
+    cerr<<"Activation of key failed"<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "check-all-zones") {
-    bool exitOnError = ((cmds.size() >= 2 ? cmds.at(1) : "") == "exit-on-error");
-    return checkAllZones(dk, exitOnError);
+  return 0;
+}
+
+static int deactivateZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() != 3) {
+    cerr << "Syntax: pdnsutil deactivate-zone-key ZONE KEY-ID"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "list-all-zones") {
-    if (cmds.size() > 2) {
-      cerr << "Syntax: pdnsutil list-all-zones [primary|secondary|native|producer|consumer]" << endl;
-      return 0;
-    }
-    if (cmds.size() == 2)
-      return listAllZones(cmds.at(1));
-    return listAllZones();
+  DNSName zone(cmds.at(1));
+  auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
+  if(!id)
+  {
+    cerr<<"Invalid KEY-ID"<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "list-member-zones") {
-    if (cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil list-member-zones CATALOG" << endl;
-      return 0;
-    }
-    return listMemberZones(cmds.at(1));
+  DNSSECKeeper dk;
+  try {
+    dk.getKeyById(zone, id);
+  } catch (std::exception& e) {
+    cerr<<e.what()<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "test-zone") {
-    cerr << "Did you mean check-zone?"<<endl;
-    return 0;
+  if (!dk.deactivateKey(zone, id)) {
+    cerr<<"Deactivation of key failed"<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "test-all-zones") {
-    cerr << "Did you mean check-all-zones?"<<endl;
+  return 0;
+}
+
+static int publishZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() != 3) {
+    cerr << "Syntax: pdnsutil publish-zone-key ZONE KEY-ID"<<endl;
     return 0;
   }
-#if 0
-  else if(cmds.at(0) == "signing-server" )
+  DNSName zone(cmds.at(1));
+  unsigned int id = atoi(cmds.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
+  if(!id)
   {
-    signingServer();
+    cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
+    return 1;
   }
-  else if(cmds.at(0) == "signing-secondary")
-  {
-    launchSigningService(0);
+  DNSSECKeeper dk;
+  try {
+    dk.getKeyById(zone, id);
+  } catch (std::exception& e) {
+    cerr<<e.what()<<endl;
+    return 1;
   }
-#endif
-  else if (cmds.at(0) == "test-speed") {
-    if(cmds.size() < 2) {
-      cerr << "Syntax: pdnsutil test-speed numcores [signing-server]"<<endl;
-      return 0;
-    }
-    testSpeed(DNSName(cmds.at(1)), (cmds.size() > 3) ? cmds.at(3) : "", pdns::checked_stoi<int>(cmds.at(2)));
+  if (!dk.publishKey(zone, id)) {
+    cerr<<"Publishing of key failed"<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "verify-crypto") {
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil verify-crypto FILE"<<endl;
-      return 0;
-    }
-    verifyCrypto(cmds.at(1));
+  return 0;
+}
+
+static int unpublishZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() != 3) {
+    cerr << "Syntax: pdnsutil unpublish-zone-key ZONE KEY-ID"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "show-zone") {
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil show-zone ZONE"<<endl;
-      return 0;
-    }
-    if (!showZone(dk, DNSName(cmds.at(1))))
-      return 1;
+  DNSName zone(cmds.at(1));
+  unsigned int id = atoi(cmds.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
+  if(!id)
+  {
+    cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "export-zone-ds") {
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil export-zone-ds ZONE"<<endl;
-      return 0;
-    }
-    if (!showZone(dk, DNSName(cmds.at(1)), true))
-      return 1;
+  DNSSECKeeper dk;
+  try {
+    dk.getKeyById(zone, id);
+  } catch (std::exception& e) {
+    cerr<<e.what()<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "disable-dnssec") {
-    if(cmds.size() != 2) {
-      cerr << "Syntax: pdnsutil disable-dnssec ZONE"<<endl;
-      return 0;
-    }
-    DNSName zone(cmds.at(1));
-    if(!disableDNSSECOnZone(dk, zone)) {
-      cerr << "Cannot disable DNSSEC on " << zone << endl;
-      return 1;
-    }
+  if (!dk.unpublishKey(zone, id)) {
+    cerr<<"Unpublishing of key failed"<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "activate-zone-key") {
-    if(cmds.size() != 3) {
-      cerr << "Syntax: pdnsutil activate-zone-key ZONE KEY-ID"<<endl;
-      return 0;
-    }
-    DNSName zone(cmds.at(1));
-    unsigned int id = atoi(cmds.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
-    if(!id)
-    {
-      cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
-      return 1;
-    }
-    try {
-      dk.getKeyById(zone, id);
-    } catch (std::exception& e) {
-      cerr<<e.what()<<endl;
-      return 1;
-    }
-    if (!dk.activateKey(zone, id)) {
-      cerr<<"Activation of key failed"<<endl;
-      return 1;
-    }
+  return 0;
+}
+
+static int addZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() < 3 ) {
+    cerr << "Syntax: pdnsutil add-zone-key ZONE [zsk|ksk] [BITS] [active|inactive] [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO_ED25519)
+    cerr << "|ed25519";
+#endif
+#if defined(HAVE_LIBCRYPTO_ED448)
+    cerr << "|ed448";
+#endif
+    cerr << "]"<<endl;
+    cerr << endl;
+    cerr << "If zsk|ksk is omitted, add-zone-key makes a key with flags 256 (a 'ZSK')."<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "deactivate-zone-key") {
-    if(cmds.size() != 3) {
-      cerr << "Syntax: pdnsutil deactivate-zone-key ZONE KEY-ID"<<endl;
-      return 0;
-    }
-    DNSName zone(cmds.at(1));
-    auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
-    if(!id)
-    {
-      cerr<<"Invalid KEY-ID"<<endl;
-      return 1;
+  DNSSECKeeper dk; //NOLINT(readability-identifier-length)
+  DNSName zone(cmds.at(1));
+
+  UeberBackend B("default"); //NOLINT(readability-identifier-length)
+  DomainInfo di; //NOLINT(readability-identifier-length)
+
+  if (!B.getDomainInfo(zone, di)){
+    cerr << "No such zone in the database" << endl;
+    return 0;
+  }
+
+  // need to get algorithm, bits & ksk or zsk from commandline
+  bool keyOrZone=false;
+  int tmp_algo=0;
+  int bits=0;
+  int algorithm=DNSSECKeeper::ECDSA256;
+  bool active=false;
+  bool published=true;
+  for(unsigned int n=2; n < cmds.size(); ++n) { //NOLINT(readability-identifier-length)
+    if (pdns_iequals(cmds.at(n), "zsk")) {
+      keyOrZone = false;
     }
-    try {
-      dk.getKeyById(zone, id);
-    } catch (std::exception& e) {
-      cerr<<e.what()<<endl;
-      return 1;
+    else if (pdns_iequals(cmds.at(n), "ksk")) {
+      keyOrZone = true;
     }
-    if (!dk.deactivateKey(zone, id)) {
-      cerr<<"Deactivation of key failed"<<endl;
-      return 1;
+    else if ((tmp_algo = DNSSECKeeper::shorthand2algorithm(cmds.at(n))) > 0) {
+      algorithm = tmp_algo;
     }
-    return 0;
-  }
-  else if (cmds.at(0) == "publish-zone-key") {
-    if(cmds.size() != 3) {
-      cerr << "Syntax: pdnsutil publish-zone-key ZONE KEY-ID"<<endl;
-      return 0;
+    else if (pdns_iequals(cmds.at(n), "active")) {
+      active=true;
     }
-    DNSName zone(cmds.at(1));
-    unsigned int id = atoi(cmds.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
-    if(!id)
-    {
-      cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
-      return 1;
+    else if (pdns_iequals(cmds.at(n), "inactive") || pdns_iequals(cmds.at(n), "passive")) { // 'passive' eventually needs to be removed
+      active=false;
     }
-    try {
-      dk.getKeyById(zone, id);
-    } catch (std::exception& e) {
-      cerr<<e.what()<<endl;
-      return 1;
+    else if (pdns_iequals(cmds.at(n), "published")) {
+      published = true;
     }
-    if (!dk.publishKey(zone, id)) {
-      cerr<<"Publishing of key failed"<<endl;
-      return 1;
+    else if (pdns_iequals(cmds.at(n), "unpublished")) {
+      published = false;
     }
-    return 0;
-  }
-  else if (cmds.at(0) == "unpublish-zone-key") {
-    if(cmds.size() != 3) {
-      cerr << "Syntax: pdnsutil unpublish-zone-key ZONE KEY-ID"<<endl;
-      return 0;
+    else if (pdns::checked_stoi<int>(cmds.at(n)) != 0) {
+      pdns::checked_stoi_into(bits, cmds.at(n));
     }
-    DNSName zone(cmds.at(1));
-    unsigned int id = atoi(cmds.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
-    if(!id)
-    {
-      cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
-      return 1;
+    else {
+      cerr << "Unknown algorithm, key flag or size '" << cmds.at(n) << "'" << endl;
+      return EXIT_FAILURE;
     }
+  }
+  int64_t id{-1}; //NOLINT(readability-identifier-length)
+  if (!dk.addKey(zone, keyOrZone, algorithm, id, bits, active, published)) {
+    cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
+    return 1;
+  }
+  cerr<<"Added a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<", active="<<active<<endl;
+  if (bits != 0) {
+    cerr<<"Requested specific key size of "<<bits<<" bits"<<endl;
+  }
+  if (id == -1) {
+    cerr<<std::to_string(id)<<": Key was added, but backend does not support returning of key id"<<endl;
+  } else if (id < -1) {
+    cerr<<std::to_string(id)<<": Key was added, but there was a failure while returning the key id"<<endl;
+    return 1;
+  } else {
     try {
       dk.getKeyById(zone, id);
+      cout<<std::to_string(id)<<endl;
     } catch (std::exception& e) {
-      cerr<<e.what()<<endl;
-      return 1;
-    }
-    if (!dk.unpublishKey(zone, id)) {
-      cerr<<"Unpublishing of key failed"<<endl;
+      cerr<<std::to_string(id)<<": Key was added, but there was a failure while reading it back: " <<e.what()<<endl;
       return 1;
     }
-    return 0;
   }
+  return 0;
+}
 
-  else if (cmds.at(0) == "add-zone-key") {
-    return addZoneKey(cmds, dk);
+static int removeZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr<<"Syntax: pdnsutil remove-zone-key ZONE KEY-ID"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "remove-zone-key") {
-    if(cmds.size() < 3) {
-      cerr<<"Syntax: pdnsutil remove-zone-key ZONE KEY-ID"<<endl;
-      return 0;
-    }
-    DNSName zone(cmds.at(1));
-    auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
-    if (!dk.removeKey(zone, id)) {
-       cerr<<"Cannot remove key " << id << " from " << zone <<endl;
-      return 1;
-    }
+  DNSSECKeeper dk;
+  DNSName zone(cmds.at(1));
+  auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
+  if (!dk.removeKey(zone, id)) {
+     cerr<<"Cannot remove key " << id << " from " << zone <<endl;
+    return 1;
+  }
+  return 0;
+}
+
+static int deleteZone(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr<<"Syntax: pdnsutil delete-zone ZONE"<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "delete-zone") {
-    if(cmds.size() != 2) {
-      cerr<<"Syntax: pdnsutil delete-zone ZONE"<<endl;
-      return 0;
-    }
-    return deleteZone(DNSName(cmds.at(1)));
+  return deleteZone(DNSName(cmds.at(1)));
+}
+
+static int createZone(vector<string>& cmds)
+{
+  if(cmds.size() != 2 && cmds.size()!=3 ) {
+    cerr<<"Syntax: pdnsutil create-zone ZONE [nsname]"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "create-zone") {
-    if(cmds.size() != 2 && cmds.size()!=3 ) {
-      cerr<<"Syntax: pdnsutil create-zone ZONE [nsname]"<<endl;
-      return 0;
-    }
-    return createZone(DNSName(cmds.at(1)), cmds.size() > 2 ? DNSName(cmds.at(2)) : DNSName());
+  return createZone(DNSName(cmds.at(1)), cmds.size() > 2 ? DNSName(cmds.at(2)) : DNSName());
+}
+
+static int createSecondaryZone(vector<string>& cmds)
+{
+  if(cmds.size() < 3 ) {
+    cerr << "Syntax: pdnsutil create-secondary-zone ZONE primary-ip [primary-ip..]" << endl;
+    return 0;
   }
-  else if (cmds.at(0) == "create-secondary-zone") {
-    if(cmds.size() < 3 ) {
-      cerr << "Syntax: pdnsutil create-secondary-zone ZONE primary-ip [primary-ip..]" << endl;
-      return 0;
-    }
-    return createSecondaryZone(cmds);
+  UeberBackend B;
+  DomainInfo di;
+  DNSName zone(cmds.at(1));
+  if (B.getDomainInfo(zone, di)) {
+    cerr << "Zone '" << zone << "' exists already" << endl;
+    return EXIT_FAILURE;
   }
-  else if (cmds.at(0) == "change-secondary-zone-primary") {
-    if(cmds.size() < 3 ) {
-      cerr << "Syntax: pdnsutil change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl;
-      return 0;
-    }
-    return changeSecondaryZonePrimary(cmds);
+  vector<ComboAddress> primaries;
+  for (unsigned i=2; i < cmds.size(); i++) {
+    primaries.emplace_back(cmds.at(i), 53);
   }
-  else if (cmds.at(0) == "add-record") {
-    if(cmds.size() < 5) {
-      cerr<<R"(Syntax: pdnsutil add-record ZONE name type [ttl] "content" ["content"...])"<<endl;
-      return 0;
-    }
-    return addOrReplaceRecord(true, cmds);
+  cerr << "Creating secondary zone '" << zone << "', with primaries '" << comboAddressVecToString(primaries) << "'" << endl;
+  B.createDomain(zone, DomainInfo::Secondary, primaries, "");
+  if(!B.getDomainInfo(zone, di)) {
+    cerr << "Zone '" << zone << "' was not created!" << endl;
+    return EXIT_FAILURE;
   }
-  else if (cmds.at(0) == "add-autoprimary" || cmds.at(0) == "add-autoprimary") {
-    if(cmds.size() < 3) {
-      cerr << "Syntax: pdnsutil add-autoprimary IP NAMESERVER [account]" << endl;
-      return 0;
-    }
-    exit(addAutoPrimary(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : ""));
+  return EXIT_SUCCESS;
+}
+
+static int changeSecondaryZonePrimary(vector<string>& cmds)
+{
+  if(cmds.size() < 3 ) {
+    cerr << "Syntax: pdnsutil change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl;
+    return 0;
   }
-  else if (cmds.at(0) == "remove-autoprimary") {
-    if(cmds.size() < 3) {
-      cerr << "Syntax: pdnsutil remove-autoprimary IP NAMESERVER" << endl;
-      return 0;
-    }
-    exit(removeAutoPrimary(cmds.at(1), cmds.at(2)));
+  UeberBackend B;
+  DomainInfo di;
+  DNSName zone(cmds.at(1));
+  if (!B.getDomainInfo(zone, di)) {
+    cerr << "Zone '" << zone << "' doesn't exist" << endl;
+    return EXIT_FAILURE;
   }
-  else if (cmds.at(0) == "list-autoprimaries") {
-    exit(listAutoPrimaries());
+  vector<ComboAddress> primaries;
+  for (unsigned i=2; i < cmds.size(); i++) {
+    primaries.emplace_back(cmds.at(i), 53);
   }
-  else if (cmds.at(0) == "replace-rrset") {
-    if(cmds.size() < 5) {
-      cerr<<R"(Syntax: pdnsutil replace-rrset ZONE name type [ttl] "content" ["content"...])"<<endl;
-      return 0;
-    }
-    return addOrReplaceRecord(false , cmds);
+  cerr << "Updating secondary zone '" << zone << "', primaries to '" << comboAddressVecToString(primaries) << "'" << endl;
+  try {
+    di.backend->setPrimaries(zone, primaries);
+    return EXIT_SUCCESS;
   }
-  else if (cmds.at(0) == "delete-rrset") {
-    if(cmds.size() != 4) {
-      cerr<<"Syntax: pdnsutil delete-rrset ZONE name type"<<endl;
-      return 0;
-    }
-    return deleteRRSet(cmds.at(1), cmds.at(2), cmds.at(3));
+  catch (PDNSException& e) {
+    cerr << "Setting primary for zone '" << zone << "' failed: " << e.reason << endl;
+    return EXIT_FAILURE;
   }
-  else if (cmds.at(0) == "list-zone") {
-    if(cmds.size() != 2) {
-      cerr<<"Syntax: pdnsutil list-zone ZONE"<<endl;
-      return 0;
-    }
-    if (cmds.at(1) == ".")
-      cmds.at(1).clear();
+}
 
-    return listZone(DNSName(cmds.at(1)));
+static int addRecord(vector<string>& cmds)
+{
+  if(cmds.size() < 5) {
+    cerr<<R"(Syntax: pdnsutil add-record ZONE name type [ttl] "content" ["content"...])"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "edit-zone") {
-    if(cmds.size() != 2) {
-      cerr<<"Syntax: pdnsutil edit-zone ZONE"<<endl;
-      return 0;
-    }
-    if (cmds.at(1) == ".")
-      cmds.at(1).clear();
+  return addOrReplaceRecord(true, cmds);
+}
 
-    PDNSColors col(g_vm.count("no-colors"));
-    return editZone(DNSName(cmds.at(1)), col);
+static int addAutoprimary(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr << "Syntax: pdnsutil add-autoprimary IP NAMESERVER [account]" << endl;
+    return 0;
   }
-  else if (cmds.at(0) == "clear-zone") {
-    if(cmds.size() != 2) {
-      cerr<<"Syntax: pdnsutil clear-zone ZONE"<<endl;
-      return 0;
-    }
-    if (cmds.at(1) == ".")
-      cmds.at(1).clear();
+  return addAutoPrimary(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : "");
+}
 
-    return clearZone(DNSName(cmds.at(1)));
+static int removeAutoprimary(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr << "Syntax: pdnsutil remove-autoprimary IP NAMESERVER" << endl;
+    return 0;
   }
-  else if (cmds.at(0) == "list-keys") {
-    if(cmds.size() > 2) {
-      cerr<<"Syntax: pdnsutil list-keys [ZONE]"<<endl;
-      return 0;
-    }
-    string zname;
-    if (cmds.size() == 2) {
-      zname = cmds.at(1);
-    }
-    return listKeys(zname, dk);
+  return removeAutoPrimary(cmds.at(1), cmds.at(2));
+}
+
+static int listAutoprimaries([[maybe_unused]] vector<string>& cmds)
+{
+  return listAutoPrimaries();
+}
+
+static int replaceRRSet(vector<string>& cmds)
+{
+  if(cmds.size() < 5) {
+    cerr<<R"(Syntax: pdnsutil replace-rrset ZONE name type [ttl] "content" ["content"...])"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "load-zone") {
-    if(cmds.size() < 3) {
-      cerr<<"Syntax: pdnsutil load-zone ZONE FILENAME [ZONE FILENAME] .."<<endl;
-      return 0;
-    }
-    if (cmds.at(1) == ".")
-      cmds.at(1).clear();
+  return addOrReplaceRecord(false , cmds);
+}
 
-    for(size_t n=1; n + 2 <= cmds.size(); n+=2) {
-      auto ret = loadZone(DNSName(cmds.at(n)), cmds.at(n + 1));
-      if (ret) exit(ret);
-    }
+static int deleteRRSet(vector<string>& cmds)
+{
+  if(cmds.size() != 4) {
+    cerr<<"Syntax: pdnsutil delete-rrset ZONE name type"<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "secure-zone") {
-    if(cmds.size() < 2) {
-      cerr << "Syntax: pdnsutil secure-zone ZONE"<<endl;
-      return 0;
-    }
-    vector<DNSName> mustRectify;
-    unsigned int zoneErrors=0;
-    for(unsigned int n = 1; n < cmds.size(); ++n) {
-      DNSName zone(cmds.at(n));
-      dk.startTransaction(zone, -1);
-      if(secureZone(dk, zone)) {
-        mustRectify.push_back(zone);
-      } else {
-        zoneErrors++;
-      }
-      dk.commitTransaction();
-    }
+  return deleteRRSet(cmds.at(1), cmds.at(2), cmds.at(3));
+}
+
+static int listZone(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr<<"Syntax: pdnsutil list-zone ZONE"<<endl;
+    return 0;
+  }
+  if (cmds.at(1) == ".")
+    cmds.at(1).clear();
 
-    for(const auto& zone : mustRectify)
-      rectifyZone(dk, zone);
+  return listZone(DNSName(cmds.at(1)));
+}
 
-    if (zoneErrors) {
-      return 1;
-    }
+static int editZone(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr<<"Syntax: pdnsutil edit-zone ZONE"<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "secure-all-zones") {
-    if (cmds.size() >= 2 && !pdns_iequals(cmds.at(1), "increase-serial")) {
-      cerr << "Syntax: pdnsutil secure-all-zones [increase-serial]"<<endl;
-      return 0;
-    }
+  if (cmds.at(1) == ".")
+    cmds.at(1).clear();
 
-    UeberBackend B("default");
+  PDNSColors col(g_vm.count("no-colors"));
+  return editZone(DNSName(cmds.at(1)), col);
+}
 
-    vector<DomainInfo> domainInfo;
-    B.getAllDomains(&domainInfo, false, false);
-
-    unsigned int zonesSecured=0, zoneErrors=0;
-    for(const DomainInfo& di :  domainInfo) {
-      if(!dk.isSecuredZone(di.zone)) {
-        cout<<"Securing "<<di.zone<<": ";
-        if (secureZone(dk, di.zone)) {
-          zonesSecured++;
-          if (cmds.size() == 2) {
-            if (!increaseSerial(di.zone, dk))
-              continue;
-          } else
-            continue;
-        }
-        zoneErrors++;
-      }
-    }
+static int clearZone(vector<string>& cmds)
+{
+  if(cmds.size() != 2) {
+    cerr<<"Syntax: pdnsutil clear-zone ZONE"<<endl;
+    return 0;
+  }
+  if (cmds.at(1) == ".")
+    cmds.at(1).clear();
 
-    cout<<"Secured: "<<zonesSecured<<" zones. Errors: "<<zoneErrors<<endl;
+  return clearZone(DNSName(cmds.at(1)));
+}
 
-    if (zoneErrors) {
-      return 1;
-    }
+static int listKeys(vector<string>& cmds)
+{
+  if(cmds.size() > 2) {
+    cerr<<"Syntax: pdnsutil list-keys [ZONE]"<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "set-kind") {
-    if(cmds.size() != 3) {
-      cerr<<"Syntax: pdnsutil set-kind ZONE KIND"<<endl;
-      return 0;
-    }
-    DNSName zone(cmds.at(1));
-    auto kind = DomainInfo::stringToKind(cmds.at(2));
-    return setZoneKind(zone, kind);
+  DNSSECKeeper dk;
+  string zname;
+  if (cmds.size() == 2) {
+    zname = cmds.at(1);
   }
-  else if (cmds.at(0) == "set-options-json") {
-    if (cmds.size() != 3) {
-      cerr << "Syntax: pdnsutil set-options ZONE VALUE" << endl;
-      return EXIT_FAILURE;
-    }
+  return listKeys(zname, dk);
+}
 
-    // Verify json
-    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;
-      }
-    }
+static int loadZone(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr<<"Syntax: pdnsutil load-zone ZONE FILENAME [ZONE FILENAME] .."<<endl;
+    return 0;
+  }
+  if (cmds.at(1) == ".")
+    cmds.at(1).clear();
 
-    DNSName zone(cmds.at(1));
+  for(size_t n=1; n + 2 <= cmds.size(); n+=2) {
+    auto ret = loadZone(DNSName(cmds.at(n)), cmds.at(n + 1));
+    if (ret) exit(ret);
+  }
+  return 0;
+}
 
-    return setZoneOptionsJson(zone, cmds.at(2));
+static int secureZone(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr << "Syntax: pdnsutil secure-zone ZONE"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "set-option") {
-    if (cmds.size() < 5 || (cmds.size() > 5 && (cmds.at(3) != "group"))) {
-      cerr << "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]1" << endl;
-      return EXIT_FAILURE;
+  DNSSECKeeper dk;
+  vector<DNSName> mustRectify;
+  unsigned int zoneErrors=0;
+  for(unsigned int n = 1; n < cmds.size(); ++n) {
+    DNSName zone(cmds.at(n));
+    dk.startTransaction(zone, -1);
+    if(secureZone(dk, zone)) {
+      mustRectify.push_back(zone);
+    } else {
+      zoneErrors++;
     }
+    dk.commitTransaction();
+  }
 
-    if ((cmds.at(2) != "producer" && cmds.at(2) != "consumer") || (cmds.at(3) != "coo" && cmds.at(3) != "unique" && cmds.at(3) != "group")) {
-      cerr << "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]" << endl;
-      return EXIT_FAILURE;
-    }
+  for(const auto& zone : mustRectify)
+    rectifyZone(dk, zone);
+
+  if (zoneErrors) {
+    return 1;
+  }
+  return 0;
+}
+
+static int secureAllZones(vector<string>& cmds)
+{
+  if (cmds.size() >= 2 && !pdns_iequals(cmds.at(1), "increase-serial")) {
+    cerr << "Syntax: pdnsutil secure-all-zones [increase-serial]"<<endl;
+    return 0;
+  }
+
+  DNSSECKeeper dk;
+  UeberBackend B("default");
 
-    DNSName zone(cmds.at(1));
-    set<string> values;
-    for (unsigned int n = 4; n < cmds.size(); ++n) {
-      if (!cmds.at(n).empty()) {
-        values.insert(cmds.at(n));
+  vector<DomainInfo> domainInfo;
+  B.getAllDomains(&domainInfo, false, false);
+
+  unsigned int zonesSecured=0, zoneErrors=0;
+  for(const DomainInfo& di :  domainInfo) {
+    if(!dk.isSecuredZone(di.zone)) {
+      cout<<"Securing "<<di.zone<<": ";
+      if (secureZone(dk, di.zone)) {
+        zonesSecured++;
+        if (cmds.size() == 2) {
+          if (!increaseSerial(di.zone, dk))
+            continue;
+        } else
+          continue;
       }
+      zoneErrors++;
     }
+  }
+
+  cout<<"Secured: "<<zonesSecured<<" zones. Errors: "<<zoneErrors<<endl;
 
-    return setZoneOption(zone, cmds.at(2), cmds.at(3), values);
+  if (zoneErrors) {
+    return 1;
   }
-  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);
+  return 0;
+}
+
+static int setKind(vector<string>& cmds)
+{
+  if(cmds.size() != 3) {
+    cerr<<"Syntax: pdnsutil set-kind ZONE KIND"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "set-account") {
-    if(cmds.size() != 3) {
-      cerr<<"Syntax: pdnsutil set-account ZONE ACCOUNT"<<endl;
-      return 0;
-    }
-    DNSName zone(cmds.at(1));
-    return setZoneAccount(zone, cmds.at(2));
+  DNSName zone(cmds.at(1));
+  auto kind = DomainInfo::stringToKind(cmds.at(2));
+  return setZoneKind(zone, kind);
+}
+
+static int setOptionsJson(vector<string>& cmds)
+{
+  if (cmds.size() != 3) {
+    cerr << "Syntax: pdnsutil set-options ZONE VALUE" << endl;
+    return EXIT_FAILURE;
   }
-  else if (cmds.at(0) == "set-nsec3") {
-    if(cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil set-nsec3 ZONE 'params' [narrow]"<<endl;
-      return 0;
-    }
-    string nsec3params = cmds.size() > 2 ? cmds.at(2) : "1 0 0 -";
-    bool narrow = cmds.size() > 3 && cmds.at(3) == "narrow";
-    NSEC3PARAMRecordContent ns3pr(nsec3params);
 
-    DNSName zone(cmds.at(1));
-    if (zone.wirelength() > 222) {
-      cerr<<"Cannot enable NSEC3 for " << zone << " as it is too long (" << zone.wirelength() << " bytes, maximum is 222 bytes)"<<endl;
-      return 1;
-    }
-    if(ns3pr.d_algorithm != 1) {
-      cerr<<"NSEC3PARAM algorithm set to '"<<std::to_string(ns3pr.d_algorithm)<<"', but '1' is the only valid value"<<endl;
+  // Verify json
+  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;
     }
-    if (! dk.setNSEC3PARAM(zone, ns3pr, narrow)) {
-      cerr<<"Cannot set NSEC3 param for " << zone << endl;
-      return 1;
-    }
+  }
 
-    if (!ns3pr.d_flags)
-      cerr<<"NSEC3 set, ";
-    else
-      cerr<<"NSEC3 (opt-out) set, ";
+  DNSName zone(cmds.at(1));
 
-    if(dk.isSecuredZone(zone))
-      cerr<<"Done, please rectify your zone if your backend needs it (or reload it if you are using the bindbackend)"<<endl;
-    else
-      cerr<<"Done, please secure and rectify your zone (or reload it if you are using the bindbackend)"<<endl;
+  return setZoneOptionsJson(zone, cmds.at(2));
+}
 
-    return 0;
+static int setOption(vector<string>& cmds)
+{
+  if (cmds.size() < 5 || (cmds.size() > 5 && (cmds.at(3) != "group"))) {
+    cerr << "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]1" << endl;
+    return EXIT_FAILURE;
   }
-  else if (cmds.at(0) == "set-presigned") {
-    if(cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil set-presigned ZONE"<<endl;
-      return 0;
-    }
-    if (!dk.setPresigned(DNSName(cmds.at(1)))) {
-      cerr << "Could not set presigned for " << cmds.at(1) << " (is DNSSEC enabled in your backend?)" << endl;
-      return 1;
-    }
-    return 0;
+
+  if ((cmds.at(2) != "producer" && cmds.at(2) != "consumer") || (cmds.at(3) != "coo" && cmds.at(3) != "unique" && cmds.at(3) != "group")) {
+    cerr << "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]" << endl;
+    return EXIT_FAILURE;
   }
-  else if (cmds.at(0) == "set-publish-cdnskey") {
-    if (cmds.size() < 2 || (cmds.size() == 3 && cmds.at(2) != "delete")) {
-      cerr<<"Syntax: pdnsutil set-publish-cdnskey ZONE [delete]"<<endl;
-      return 0;
-    }
-    if (!dk.setPublishCDNSKEY(DNSName(cmds.at(1)), (cmds.size() == 3 && cmds.at(2) == "delete"))) {
-      cerr << "Could not set publishing for CDNSKEY records for " << cmds.at(1) << endl;
-      return 1;
+
+  DNSName zone(cmds.at(1));
+  set<string> values;
+  for (unsigned int n = 4; n < cmds.size(); ++n) {
+    if (!cmds.at(n).empty()) {
+      values.insert(cmds.at(n));
     }
-    return 0;
   }
-  else if (cmds.at(0) == "set-publish-cds") {
-    if(cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil set-publish-cds ZONE [DIGESTALGOS]"<<endl;
-      return 0;
-    }
 
-    // If DIGESTALGOS is unset
-    if(cmds.size() == 2)
-      cmds.push_back("2");
+  return setZoneOption(zone, cmds.at(2), cmds.at(3), values);
+}
 
-    if (!dk.setPublishCDS(DNSName(cmds.at(1)), cmds.at(2))) {
-      cerr << "Could not set publishing for CDS records for " << cmds.at(1) << endl;
-      return 1;
-    }
-    return 0;
-  }
-  else if (cmds.at(0) == "unset-presigned") {
-    if(cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil unset-presigned ZONE"<<endl;
-      return 0;
-    }
-    if (!dk.unsetPresigned(DNSName(cmds.at(1)))) {
-      cerr << "Could not unset presigned on for " << cmds.at(1) << endl;
-      return 1;
-    }
+static int setCatalog(vector<string>& cmds)
+{
+  if (cmds.size() != 3) {
+    cerr << "Syntax: pdnsutil set-catalog ZONE CATALOG" << endl;
     return 0;
   }
-  else if (cmds.at(0) == "unset-publish-cdnskey") {
-    if(cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil unset-publish-cdnskey ZONE"<<endl;
-      return 0;
-    }
-    if (!dk.unsetPublishCDNSKEY(DNSName(cmds.at(1)))) {
-      cerr << "Could not unset publishing for CDNSKEY records for " << cmds.at(1) << endl;
-      return 1;
-    }
-    return 0;
+  DNSName zone(cmds.at(1));
+  DNSName catalog; // Create an empty DNSName()
+  if (!cmds.at(2).empty()) {
+    catalog = DNSName(cmds.at(2));
   }
-  else if (cmds.at(0) == "unset-publish-cds") {
-    if(cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil unset-publish-cds ZONE"<<endl;
-      return 0;
-    }
-    if (!dk.unsetPublishCDS(DNSName(cmds.at(1)))) {
-      cerr << "Could not unset publishing for CDS records for " << cmds.at(1) << endl;
-      return 1;
-    }
+  return setZoneCatalog(zone, catalog);
+}
+
+static int setAccount(vector<string>& cmds)
+{
+  if(cmds.size() != 3) {
+    cerr<<"Syntax: pdnsutil set-account ZONE ACCOUNT"<<endl;
     return 0;
   }
-  else if(cmds.at(0) == "hash-password") {
-    if (cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil hash-password PASSWORD"<<endl;
-      return 0;
-    }
-    cout<<hashPassword(cmds.at(1))<<endl;
+  DNSName zone(cmds.at(1));
+  return setZoneAccount(zone, cmds.at(2));
+}
+
+static int setNsec3(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr<<"Syntax: pdnsutil set-nsec3 ZONE 'params' [narrow]"<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "hash-zone-record") {
-    if(cmds.size() < 3) {
-      cerr<<"Syntax: pdnsutil hash-zone-record ZONE RNAME"<<endl;
-      return 0;
-    }
-    DNSName zone(cmds.at(1));
-    DNSName record(cmds.at(2));
-    NSEC3PARAMRecordContent ns3pr;
-    bool narrow = false;
-    if(!dk.getNSEC3PARAM(zone, &ns3pr, &narrow)) {
-      cerr<<"The '"<<zone<<"' zone does not use NSEC3"<<endl;
-      return 0;
-    }
-    if(narrow) {
-      cerr<<"The '"<<zone<<"' zone uses narrow NSEC3, but calculating hash anyhow"<<endl;
-    }
+  string nsec3params = cmds.size() > 2 ? cmds.at(2) : "1 0 0 -";
+  bool narrow = cmds.size() > 3 && cmds.at(3) == "narrow";
+  NSEC3PARAMRecordContent ns3pr(nsec3params);
 
-    cout<<toBase32Hex(hashQNameWithSalt(ns3pr, record))<<endl;
+  DNSSECKeeper dk;
+  DNSName zone(cmds.at(1));
+  if (zone.wirelength() > 222) {
+    cerr<<"Cannot enable NSEC3 for " << zone << " as it is too long (" << zone.wirelength() << " bytes, maximum is 222 bytes)"<<endl;
+    return 1;
   }
-  else if (cmds.at(0) == "unset-nsec3") {
-    if(cmds.size() < 2) {
-      cerr<<"Syntax: pdnsutil unset-nsec3 ZONE"<<endl;
-      return 0;
-    }
-    if (!dk.unsetNSEC3PARAM(DNSName(cmds.at(1)))) {
-      cerr << "Cannot unset NSEC3 param for " << cmds.at(1) << endl;
-      return 1;
-    }
+  if(ns3pr.d_algorithm != 1) {
+    cerr<<"NSEC3PARAM algorithm set to '"<<std::to_string(ns3pr.d_algorithm)<<"', but '1' is the only valid value"<<endl;
+    return EXIT_FAILURE;
+  }
+  if (! dk.setNSEC3PARAM(zone, ns3pr, narrow)) {
+    cerr<<"Cannot set NSEC3 param for " << zone << endl;
+    return 1;
+  }
+
+  if (!ns3pr.d_flags)
+    cerr<<"NSEC3 set, ";
+  else
+    cerr<<"NSEC3 (opt-out) set, ";
+
+  if(dk.isSecuredZone(zone))
     cerr<<"Done, please rectify your zone if your backend needs it (or reload it if you are using the bindbackend)"<<endl;
+  else
+    cerr<<"Done, please secure and rectify your zone (or reload it if you are using the bindbackend)"<<endl;
+
+  return 0;
+}
 
+static int setPresigned(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr<<"Syntax: pdnsutil set-presigned ZONE"<<endl;
     return 0;
   }
-  else if (cmds.at(0) == "export-zone-key") {
-    if (cmds.size() < 3) {
-      cerr << "Syntax: pdnsutil export-zone-key ZONE KEY-ID" << endl;
-      return 1;
-    }
-
-    string zone = cmds.at(1);
-    auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
-    DNSSECPrivateKey dpk = dk.getKeyById(DNSName(zone), id);
-    cout << dpk.getKey()->convertToISC() << endl;
+  DNSSECKeeper dk;
+  if (!dk.setPresigned(DNSName(cmds.at(1)))) {
+    cerr << "Could not set presigned for " << cmds.at(1) << " (is DNSSEC enabled in your backend?)" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "export-zone-key-pem") {
-    if (cmds.size() < 3) {
-      cerr << "Syntax: pdnsutil export-zone-key-pem ZONE KEY-ID" << endl;
-      return 1;
-    }
+  return 0;
+}
 
-    string zone = cmds.at(1);
-    auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
-    DNSSECPrivateKey dpk = dk.getKeyById(DNSName(zone), id);
-    dpk.getKey()->convertToPEMFile(*stdout);
+static int setPublishCDNSKey(vector<string>& cmds)
+{
+  if (cmds.size() < 2 || (cmds.size() == 3 && cmds.at(2) != "delete")) {
+    cerr<<"Syntax: pdnsutil set-publish-cdnskey ZONE [delete]"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "increase-serial") {
-    if (cmds.size() < 2) {
-      cerr << "Syntax: pdnsutil increase-serial ZONE" << endl;
-      return 1;
-    }
-    return increaseSerial(DNSName(cmds.at(1)), dk);
+  DNSSECKeeper dk;
+  if (!dk.setPublishCDNSKEY(DNSName(cmds.at(1)), (cmds.size() == 3 && cmds.at(2) == "delete"))) {
+    cerr << "Could not set publishing for CDNSKEY records for " << cmds.at(1) << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "import-zone-key-pem") {
-    if (cmds.size() < 4) {
-      cerr << "Syntax: pdnsutil import-zone-key-pem ZONE FILE ALGORITHM {ksk|zsk}" << endl;
-      return 1;
-    }
-
-    const string zone = cmds.at(1);
-    const string filename = cmds.at(2);
-    const auto algorithm = pdns::checked_stoi<unsigned int>(cmds.at(3));
+  return 0;
+}
 
-    errno = 0;
-    pdns::UniqueFilePtr filePtr{std::fopen(filename.c_str(), "r")};
-    if (filePtr == nullptr) {
-      auto errMsg = pdns::getMessageFromErrno(errno);
-      throw runtime_error("Failed to open PEM file `" + filename + "`: " + errMsg);
-    }
+static int setPublishCDs(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr<<"Syntax: pdnsutil set-publish-cds ZONE [DIGESTALGOS]"<<endl;
+    return 0;
+  }
 
-    DNSKEYRecordContent drc;
-    shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *filePtr, filename)};
-    if (!key) {
-      cerr << "Could not convert key from PEM to internal format" << endl;
-      return 1;
-    }
+  // If DIGESTALGOS is unset
+  if(cmds.size() == 2)
+    cmds.push_back("2");
 
-    DNSSECPrivateKey dpk;
+  DNSSECKeeper dk;
+  if (!dk.setPublishCDS(DNSName(cmds.at(1)), cmds.at(2))) {
+    cerr << "Could not set publishing for CDS records for " << cmds.at(1) << endl;
+    return 1;
+  }
+  return 0;
+}
 
-    uint8_t algo = 0;
-    pdns::checked_stoi_into(algo, cmds.at(3));
-    if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
-      algo = DNSSECKeeper::RSASHA1;
-    }
+static int unsetPresigned(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr<<"Syntax: pdnsutil unset-presigned ZONE"<<endl;
+    return 0;
+  }
+  DNSSECKeeper dk;
+  if (!dk.unsetPresigned(DNSName(cmds.at(1)))) {
+    cerr << "Could not unset presigned on for " << cmds.at(1) << endl;
+    return 1;
+  }
+  return 0;
+}
 
-    cerr << std::to_string(algo) << endl;
+static int unsetPublishCDNSKey(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr<<"Syntax: pdnsutil unset-publish-cdnskey ZONE"<<endl;
+    return 0;
+  }
+  DNSSECKeeper dk;
+  if (!dk.unsetPublishCDNSKEY(DNSName(cmds.at(1)))) {
+    cerr << "Could not unset publishing for CDNSKEY records for " << cmds.at(1) << endl;
+    return 1;
+  }
+  return 0;
+}
 
-    uint16_t flags = 0;
-    if (cmds.size() > 4) {
-      if (pdns_iequals(cmds.at(4), "ZSK")) {
-        flags = 256;
-      }
-      else if (pdns_iequals(cmds.at(4), "KSK")) {
-        flags = 257;
-      }
-      else {
-        cerr << "Unknown key flag '" << cmds.at(4) << "'" << endl;
-        return 1;
-      }
-    }
-    else {
-      flags = 257; // ksk
-    }
-    dpk.setKey(key, flags, algo);
+static int unsetPublishCDs(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr<<"Syntax: pdnsutil unset-publish-cds ZONE"<<endl;
+    return 0;
+  }
+  DNSSECKeeper dk;
+  if (!dk.unsetPublishCDS(DNSName(cmds.at(1)))) {
+    cerr << "Could not unset publishing for CDS records for " << cmds.at(1) << endl;
+    return 1;
+  }
+  return 0;
+}
 
-    int64_t id{-1};
-    if (!dk.addKey(DNSName(zone), dpk, id)) {
-      cerr << "Adding key failed, perhaps DNSSEC not enabled in configuration?" << endl;
-      return 1;
-    }
+static int hashZoneRecord(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr<<"Syntax: pdnsutil hash-zone-record ZONE RNAME"<<endl;
+    return 0;
+  }
+  DNSSECKeeper dk;
+  DNSName zone(cmds.at(1));
+  DNSName record(cmds.at(2));
+  NSEC3PARAMRecordContent ns3pr;
+  bool narrow = false;
+  if(!dk.getNSEC3PARAM(zone, &ns3pr, &narrow)) {
+    cerr<<"The '"<<zone<<"' zone does not use NSEC3"<<endl;
+    return 0;
+  }
+  if(narrow) {
+    cerr<<"The '"<<zone<<"' zone uses narrow NSEC3, but calculating hash anyhow"<<endl;
+  }
 
-    if (id == -1) {
-      cerr << std::to_string(id) << "Key was added, but backend does not support returning of key id" << endl;
-    }
-    else if (id < -1) {
-      cerr << std::to_string(id) << "Key was added, but there was a failure while returning the key id" << endl;
-    }
-    else {
-      cout << std::to_string(id) << endl;
-    }
+  cout<<toBase32Hex(hashQNameWithSalt(ns3pr, record))<<endl;
+  return 0;
+}
+
+static int unsetNSec3(vector<string>& cmds)
+{
+  if(cmds.size() < 2) {
+    cerr<<"Syntax: pdnsutil unset-nsec3 ZONE"<<endl;
+    return 0;
   }
-  else if (cmds.at(0) == "import-zone-key") {
-    if(cmds.size() < 3) {
-      cerr<<"Syntax: pdnsutil import-zone-key ZONE FILE [ksk|zsk] [active|inactive]"<<endl;
-      return 1;
-    }
-    string zone = cmds.at(1);
-    string fname = cmds.at(2);
-    DNSKEYRecordContent drc;
-    shared_ptr<DNSCryptoKeyEngine> key(DNSCryptoKeyEngine::makeFromISCFile(drc, fname.c_str()));
-
-    uint16_t flags = 257;
-    bool active=true;
-    bool published=true;
-
-    for(unsigned int n = 3; n < cmds.size(); ++n) {
-      if (pdns_iequals(cmds.at(n), "ZSK"))
-        flags = 256;
-      else if (pdns_iequals(cmds.at(n), "KSK"))
-        flags = 257;
-      else if (pdns_iequals(cmds.at(n), "active"))
-        active = true;
-      else if (pdns_iequals(cmds.at(n), "passive") || pdns_iequals(cmds.at(n), "inactive")) // passive eventually needs to be removed
-        active = false;
-      else if (pdns_iequals(cmds.at(n), "published"))
-        published = true;
-      else if (pdns_iequals(cmds.at(n), "unpublished"))
-        published = false;
-      else {
-        cerr << "Unknown key flag '" << cmds.at(n) << "'" << endl;
-        return 1;
-      }
-    }
+  DNSSECKeeper dk;
+  if (!dk.unsetNSEC3PARAM(DNSName(cmds.at(1)))) {
+    cerr << "Cannot unset NSEC3 param for " << cmds.at(1) << endl;
+    return 1;
+  }
+  cerr<<"Done, please rectify your zone if your backend needs it (or reload it if you are using the bindbackend)"<<endl;
 
-    DNSSECPrivateKey dpk;
-    uint8_t algo = key->getAlgorithm();
-    if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
-      algo = DNSSECKeeper::RSASHA1;
-    }
-    dpk.setKey(key, flags, algo);
+  return 0;
+}
 
-    int64_t id{-1};
-    if (!dk.addKey(DNSName(zone), dpk, id, active, published)) {
-      cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
-      return 1;
-    }
-    if (id == -1) {
-      cerr<<std::to_string(id)<<"Key was added, but backend does not support returning of key id"<<endl;
-    } else if (id < -1) {
-      cerr<<std::to_string(id)<<"Key was added, but there was a failure while returning the key id"<<endl;
-    } else {
-      cout<<std::to_string(id)<<endl;
-    }
+static int exportZoneKey(vector<string>& cmds)
+{
+  if (cmds.size() < 3) {
+    cerr << "Syntax: pdnsutil export-zone-key ZONE KEY-ID" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "export-zone-dnskey") {
-    if(cmds.size() < 3) {
-      cerr<<"Syntax: pdnsutil export-zone-dnskey ZONE KEY-ID"<<endl;
-      return 1;
-    }
 
-    DNSName zone(cmds.at(1));
-    auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
-    DNSSECPrivateKey dpk=dk.getKeyById(zone, id);
-    cout << zone<<" IN DNSKEY "<<dpk.getDNSKEY().getZoneRepresentation() <<endl;
+  DNSSECKeeper dk;
+  string zone = cmds.at(1);
+  auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
+  DNSSECPrivateKey dpk = dk.getKeyById(DNSName(zone), id);
+  cout << dpk.getKey()->convertToISC() << endl;
+  return 0;
+}
+
+static int exportZoneKeyPEM(vector<string>& cmds)
+{
+  if (cmds.size() < 3) {
+    cerr << "Syntax: pdnsutil export-zone-key-pem ZONE KEY-ID" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "generate-zone-key") {
-    if(cmds.size() < 2 ) {
-      cerr << "Syntax: pdnsutil generate-zone-key zsk|ksk [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
-#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO_ED25519)
-      cerr << "|ed25519";
-#endif
-#if defined(HAVE_LIBCRYPTO_ED448)
-      cerr << "|ed448";
-#endif
-      cerr << "] [bits]"<<endl;
-      return 0;
-    }
-    // need to get algorithm, bits & ksk or zsk from commandline
-    bool keyOrZone=false;
-    int tmp_algo=0;
-    int bits=0;
-    int algorithm=DNSSECKeeper::ECDSA256;
-    for(unsigned int n=1; n < cmds.size(); ++n) {
-      if (pdns_iequals(cmds.at(n), "zsk"))
-        keyOrZone = false;
-      else if (pdns_iequals(cmds.at(n), "ksk"))
-        keyOrZone = true;
-      else if ((tmp_algo = DNSSECKeeper::shorthand2algorithm(cmds.at(n))) > 0) {
-        algorithm = tmp_algo;
-      }
-      else if (pdns::checked_stoi<int>(cmds.at(n)) != 0)
-        pdns::checked_stoi_into(bits, cmds.at(n));
-      else {
-        cerr << "Unknown algorithm, key flag or size '" << cmds.at(n) << "'" << endl;
-        return 0;
-      }
-    }
-    cerr<<"Generating a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<endl;
-    if(bits)
-      cerr<<"Requesting specific key size of "<<bits<<" bits"<<endl;
 
-    shared_ptr<DNSCryptoKeyEngine> dpk(DNSCryptoKeyEngine::make(algorithm));
-    if(!bits) {
-      if(algorithm <= 10)
-        bits = keyOrZone ? 2048 : 1024;
-      else {
-        if(algorithm == DNSSECKeeper::ECCGOST || algorithm == DNSSECKeeper::ECDSA256 || algorithm == DNSSECKeeper::ED25519)
-          bits = 256;
-        else if(algorithm == DNSSECKeeper::ECDSA384)
-          bits = 384;
-        else if(algorithm == DNSSECKeeper::ED448)
-          bits = 456;
-        else {
-          throw runtime_error("Can not guess key size for algorithm "+std::to_string(algorithm));
-        }
-      }
-    }
-    dpk->create(bits);
-    DNSSECPrivateKey dspk;
-    dspk.setKey(dpk, keyOrZone ? 257 : 256, algorithm);
+  DNSSECKeeper dk;
+  string zone = cmds.at(1);
+  auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
+  DNSSECPrivateKey dpk = dk.getKeyById(DNSName(zone), id);
+  dpk.getKey()->convertToPEMFile(*stdout);
+  return 0;
+}
 
-    // print key to stdout
-    cout << "Flags: " << dspk.getFlags() << endl <<
-             dspk.getKey()->convertToISC() << endl;
+static int increaseSerial(vector<string>& cmds)
+{
+  if (cmds.size() < 2) {
+    cerr << "Syntax: pdnsutil increase-serial ZONE" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "generate-tsig-key") {
-    string usage = "Syntax: " + cmds.at(0) + " name (hmac-md5|hmac-sha1|hmac-sha224|hmac-sha256|hmac-sha384|hmac-sha512)";
-    if (cmds.size() < 3) {
-      cerr << usage << endl;
-      return 0;
-    }
-    DNSName name(cmds.at(1));
-    DNSName algo(cmds.at(2));
-    string key;
-    try {
-      key = makeTSIGKey(algo);
-    } catch(const PDNSException& e) {
-      cerr << "Could not create new TSIG key " << name << " " << algo << ": "<< e.reason << endl;
-      return 1;
-    }
+  DNSSECKeeper dk;
+  return increaseSerial(DNSName(cmds.at(1)), dk);
+}
 
-    UeberBackend B("default");
-    if (B.setTSIGKey(name, DNSName(algo), key)) { // you are feeling bored, put up DNSName(algo) up earlier
-      cout << "Create new TSIG key " << name << " " << algo << " " << key << endl;
-    } else {
-      cerr << "Failure storing new TSIG key " << name << " " << algo << " " << key << endl;
-      return 1;
-    }
-    return 0;
+static int importZoneKeyPEM(vector<string>& cmds)
+{
+  if (cmds.size() < 4) {
+    cerr << "Syntax: pdnsutil import-zone-key-pem ZONE FILE ALGORITHM {ksk|zsk}" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "import-tsig-key") {
-    if (cmds.size() < 4) {
-      cerr << "Syntax: " << cmds.at(0) << " name algorithm key" << endl;
-      return 0;
-    }
-    DNSName name(cmds.at(1));
-    string algo = cmds.at(2);
-    string key = cmds.at(3);
 
-    UeberBackend B("default");
-    if (B.setTSIGKey(name, DNSName(algo), key)) {
-      cout << "Imported TSIG key " << name << " " << algo << endl;
-    }
-    else {
-      cerr << "Failure importing TSIG key " << name << " " << algo << endl;
-      return 1;
-    }
-    return 0;
+  const string zone = cmds.at(1);
+  const string filename = cmds.at(2);
+  const auto algorithm = pdns::checked_stoi<unsigned int>(cmds.at(3));
+
+  errno = 0;
+  pdns::UniqueFilePtr filePtr{std::fopen(filename.c_str(), "r")};
+  if (filePtr == nullptr) {
+    auto errMsg = pdns::getMessageFromErrno(errno);
+    throw runtime_error("Failed to open PEM file `" + filename + "`: " + errMsg);
   }
-  else if (cmds.at(0) == "delete-tsig-key") {
-    if (cmds.size() < 2) {
-      cerr << "Syntax: " << cmds.at(0) << " name" << endl;
-      return 0;
-    }
-    DNSName name(cmds.at(1));
 
-    UeberBackend B("default");
-    if (B.deleteTSIGKey(name)) {
-      cout << "Deleted TSIG key " << name << endl;
-    }
-    else {
-      cerr << "Failure deleting TSIG key " << name << endl;
-      return 1;
-    }
-    return 0;
+  DNSKEYRecordContent drc;
+  shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *filePtr, filename)};
+  if (!key) {
+    cerr << "Could not convert key from PEM to internal format" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "list-tsig-keys") {
-    std::vector<struct TSIGKey> keys;
-    UeberBackend B("default");
-    if (B.getTSIGKeys(keys)) {
-      for (const TSIGKey& key : keys) {
-        cout << key.name.toString() << " " << key.algorithm.toString() << " " << key.key << endl;
-      }
-    }
-    return 0;
+
+  DNSSECPrivateKey dpk;
+
+  uint8_t algo = 0;
+  pdns::checked_stoi_into(algo, cmds.at(3));
+  if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
+    algo = DNSSECKeeper::RSASHA1;
   }
-  else if (cmds.at(0) == "activate-tsig-key") {
-    string metaKey;
-    if (cmds.size() < 4) {
-      cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary}" << endl;
-      return 0;
-    }
-    DNSName zname(cmds.at(1));
-    string name = cmds.at(2);
-    if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
-      metaKey = "TSIG-ALLOW-AXFR";
-    else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
-      metaKey = "AXFR-MASTER-TSIG";
-    else {
-      cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
-      return 1;
-    }
-    UeberBackend B("default");
-    DomainInfo di;
-    if (!B.getDomainInfo(zname, di)) {
-      cerr << "Zone '" << zname << "' does not exist" << endl;
-      return 1;
-    }
-    std::vector<std::string> meta;
-    if (!B.getDomainMetadata(zname, metaKey, meta)) {
-      cerr << "Failure enabling TSIG key " << name << " for " << zname << endl;
-      return 1;
-    }
-    bool found = false;
-    for (const std::string& tmpname : meta) {
-      if (tmpname == name) {
-        found = true;
-        break;
-      }
+
+  cerr << std::to_string(algo) << endl;
+
+  uint16_t flags = 0;
+  if (cmds.size() > 4) {
+    if (pdns_iequals(cmds.at(4), "ZSK")) {
+      flags = 256;
     }
-    if (!found)
-      meta.push_back(name);
-    if (B.setDomainMetadata(zname, metaKey, meta)) {
-      cout << "Enabled TSIG key " << name << " for " << zname << endl;
+    else if (pdns_iequals(cmds.at(4), "KSK")) {
+      flags = 257;
     }
     else {
-      cerr << "Failure enabling TSIG key " << name << " for " << zname << endl;
+      cerr << "Unknown key flag '" << cmds.at(4) << "'" << endl;
       return 1;
     }
-    return 0;
   }
-  else if (cmds.at(0) == "deactivate-tsig-key") {
-    string metaKey;
-    if (cmds.size() < 4) {
-      cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary|producer|consumer}" << endl;
-      return 0;
-    }
-    DNSName zname(cmds.at(1));
-    string name = cmds.at(2);
-    if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
-      metaKey = "TSIG-ALLOW-AXFR";
-    else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
-      metaKey = "AXFR-MASTER-TSIG";
-    else {
-      cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
-      return 1;
-    }
+  else {
+    flags = 257; // ksk
+  }
+  dpk.setKey(key, flags, algo);
 
-    UeberBackend B("default");
-    DomainInfo di;
-    if (!B.getDomainInfo(zname, di)) {
-      cerr << "Zone '" << zname << "' does not exist" << endl;
-      return 1;
-    }
-    std::vector<std::string> meta;
-    if (!B.getDomainMetadata(zname, metaKey, meta)) {
-      cerr << "Failure disabling TSIG key " << name << " for " << zname << endl;
-      return 1;
-    }
-    std::vector<std::string>::iterator iter = meta.begin();
-    for (; iter != meta.end(); ++iter)
-      if (*iter == name)
-        break;
-    if (iter != meta.end())
-      meta.erase(iter);
-    if (B.setDomainMetadata(zname, metaKey, meta)) {
-      cout << "Disabled TSIG key " << name << " for " << zname << endl;
-    }
-    else {
-      cerr << "Failure disabling TSIG key " << name << " for " << zname << endl;
-      return 1;
-    }
-    return 0;
+  DNSSECKeeper dk;
+  int64_t id{-1};
+  if (!dk.addKey(DNSName(zone), dpk, id)) {
+    cerr << "Adding key failed, perhaps DNSSEC not enabled in configuration?" << endl;
+    return 1;
   }
-  else if (cmds.at(0) == "get-meta") {
-    UeberBackend B("default");
-    if (cmds.size() < 2) {
-      cerr << "Syntax: " << cmds.at(0) << " zone [kind kind ..]" << endl;
-      return 1;
-    }
-    DNSName zone(cmds.at(1));
-    vector<string> keys;
 
-    DomainInfo di;
-    if (!B.getDomainInfo(zone, di)) {
-       cerr << "Invalid zone '" << zone << "'" << endl;
-       return 1;
-    }
-
-    if (cmds.size() > 2) {
-      keys.assign(cmds.begin() + 2, cmds.end());
-      std::cout << "Metadata for '" << zone << "'" << endl;
-      for(const auto& kind :  keys) {
-        vector<string> meta;
-        meta.clear();
-        if (B.getDomainMetadata(zone, kind, meta)) {
-          cout << kind << " = " << boost::join(meta, ", ") << endl;
-        }
-      }
-    } else {
-      std::map<std::string, std::vector<std::string> > meta;
-      std::cout << "Metadata for '" << zone << "'" << endl;
-      B.getAllDomainMetadata(zone, meta);
-      for(const auto& each_meta: meta) {
-        cout << each_meta.first << " = " << boost::join(each_meta.second, ", ") << endl;
-      }
+  if (id == -1) {
+    cerr << std::to_string(id) << "Key was added, but backend does not support returning of key id" << endl;
     }
-    return 0;
+  else if (id < -1) {
+    cerr << std::to_string(id) << "Key was added, but there was a failure while returning the key id" << endl;
   }
-  else if (cmds.at(0) == "set-meta" || cmds.at(0) == "add-meta") {
-    if (cmds.size() < 3) {
-      cerr << "Syntax: " << cmds.at(0) << " ZONE KIND [VALUE VALUE ..]" << endl;
-      return 1;
-    }
-    DNSName zone(cmds.at(1));
-    string kind = cmds.at(2);
-    const static std::array<string, 7> multiMetaWhitelist = {"ALLOW-AXFR-FROM", "ALLOW-DNSUPDATE-FROM",
-      "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE", "GSS-ALLOW-AXFR-PRINCIPAL",
-      "PUBLISH-CDS"};
-    bool clobber = true;
-    if (cmds.at(0) == "add-meta") {
-      clobber = false;
-      if (find(multiMetaWhitelist.begin(), multiMetaWhitelist.end(), kind) == multiMetaWhitelist.end() && kind.find("X-") != 0) {
-        cerr<<"Refusing to add metadata to single-value metadata "<<kind<<endl;
-        return 1;
-      }
-    }
-    vector<string> meta(cmds.begin() + 3, cmds.end());
-    return addOrSetMeta(zone, kind, meta, clobber);
+  else {
+    cout << std::to_string(id) << endl;
   }
-  else if (cmds.at(0) == "hsm") {
-#ifdef HAVE_P11KIT1
-    UeberBackend B("default");
-    if (cmds.size() < 2) {
-      cerr << "Missing sub-command for pdnsutil hsm"<< std::endl;
-      return 0;
-    }
-    else if (cmds.at(1) == "assign") {
-      DNSCryptoKeyEngine::storvector_t storvect;
-      DomainInfo di;
-      std::vector<DNSBackend::KeyData> keys;
-
-      if (cmds.size() < 9) {
-        std::cout << "Usage: pdnsutil hsm assign ZONE ALGORITHM {ksk|zsk} MODULE TOKEN PIN LABEL (PUBLABEL)" << std::endl;
-        return 1;
-      }
-
-      DNSName zone(cmds.at(2));
-
-      // verify zone
-      if (!B.getDomainInfo(zone, di)) {
-        cerr << "Unable to assign module to unknown zone '" << zone << "'" << std::endl;
-        return 1;
-      }
+  return 0;
+}
 
-      int algorithm = DNSSECKeeper::shorthand2algorithm(cmds.at(3));
-      if (algorithm<0) {
-        cerr << "Unable to use unknown algorithm '" << cmds.at(3) << "'" << std::endl;
-        return 1;
-      }
+static int importZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr<<"Syntax: pdnsutil import-zone-key ZONE FILE [ksk|zsk] [active|inactive]"<<endl;
+    return 1;
+  }
+  string zone = cmds.at(1);
+  string fname = cmds.at(2);
+  DNSKEYRecordContent drc;
+  shared_ptr<DNSCryptoKeyEngine> key(DNSCryptoKeyEngine::makeFromISCFile(drc, fname.c_str()));
 
-      bool keyOrZone = (cmds.at(4) == "ksk" ? true : false);
-      string module = cmds.at(5);
-      string slot = cmds.at(6);
-      string pin = cmds.at(7);
-      string label = cmds.at(8);
-      string pub_label;
-      if (cmds.size() > 9)
-        pub_label = cmds.at(9);
-      else
-         pub_label = label;
-
-      std::ostringstream iscString;
-      iscString << "Private-key-format: v1.2" << std::endl <<
-        "Algorithm: " << algorithm << std::endl <<
-        "Engine: " << module << std::endl <<
-        "Slot: " << slot << std::endl <<
-        "PIN: " << pin << std::endl <<
-        "Label: " << label << std::endl <<
-        "PubLabel: " << pub_label << std::endl;
-
-      DNSKEYRecordContent drc;
-
-      shared_ptr<DNSCryptoKeyEngine> dke(DNSCryptoKeyEngine::makeFromISCString(drc, iscString.str()));
-      if(!dke->checkKey()) {
-        cerr << "Invalid DNS Private Key in engine " << module << " slot " << slot << std::endl;
-        return 1;
-      }
-      DNSSECPrivateKey dpk;
-      dpk.setKey(dke, keyOrZone ? 257 : 256);
+  uint16_t flags = 257;
+  bool active=true;
+  bool published=true;
 
-      // make sure this key isn't being reused.
-      B.getDomainKeys(zone, keys);
+  for(unsigned int n = 3; n < cmds.size(); ++n) {
+    if (pdns_iequals(cmds.at(n), "ZSK"))
+      flags = 256;
+    else if (pdns_iequals(cmds.at(n), "KSK"))
+      flags = 257;
+    else if (pdns_iequals(cmds.at(n), "active"))
+      active = true;
+    else if (pdns_iequals(cmds.at(n), "passive") || pdns_iequals(cmds.at(n), "inactive")) // passive eventually needs to be removed
+      active = false;
+    else if (pdns_iequals(cmds.at(n), "published"))
+      published = true;
+    else if (pdns_iequals(cmds.at(n), "unpublished"))
+      published = false;
+    else {
+      cerr << "Unknown key flag '" << cmds.at(n) << "'" << endl;
+      return 1;
+    }
+  }
 
-      int64_t id{-1};
-      for(DNSBackend::KeyData& kd :  keys) {
-        if (kd.content == iscString.str()) {
-          // it's this one, I guess...
-          id = kd.id;
-          break;
-        }
-      }
+  DNSSECPrivateKey dpk;
+  uint8_t algo = key->getAlgorithm();
+  if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
+    algo = DNSSECKeeper::RSASHA1;
+  }
+  dpk.setKey(key, flags, algo);
 
-      if (id > -1) {
-        cerr << "You have already assigned this key with ID=" << id << std::endl;
-        return 1;
-      }
+  DNSSECKeeper dk;
+  int64_t id{-1};
+  if (!dk.addKey(DNSName(zone), dpk, id, active, published)) {
+    cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
+    return 1;
+  }
+  if (id == -1) {
+    cerr<<std::to_string(id)<<"Key was added, but backend does not support returning of key id"<<endl;
+  } else if (id < -1) {
+    cerr<<std::to_string(id)<<"Key was added, but there was a failure while returning the key id"<<endl;
+  } else {
+    cout<<std::to_string(id)<<endl;
+  }
+  return 0;
+}
 
-      if (!dk.addKey(zone, dpk, id)) {
-        cerr << "Unable to assign module slot to zone" << std::endl;
-        return 1;
-      }
+static int expotZoneDNSKey(vector<string>& cmds)
+{
+  if(cmds.size() < 3) {
+    cerr<<"Syntax: pdnsutil export-zone-dnskey ZONE KEY-ID"<<endl;
+    return 1;
+  }
 
-      cerr << "Module " << module << " slot " << slot << " assigned to " << zone << " with key id " << id << endl;
+  DNSSECKeeper dk;
+  DNSName zone(cmds.at(1));
+  auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
+  DNSSECPrivateKey dpk=dk.getKeyById(zone, id);
+  cout << zone<<" IN DNSKEY "<<dpk.getDNSKEY().getZoneRepresentation() <<endl;
+  return 0;
+}
 
+static int generateZoneKey(vector<string>& cmds)
+{
+  if(cmds.size() < 2 ) {
+    cerr << "Syntax: pdnsutil generate-zone-key zsk|ksk [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO_ED25519)
+    cerr << "|ed25519";
+#endif
+#if defined(HAVE_LIBCRYPTO_ED448)
+    cerr << "|ed448";
+#endif
+    cerr << "] [bits]"<<endl;
+    return 0;
+  }
+  // need to get algorithm, bits & ksk or zsk from commandline
+  bool keyOrZone=false;
+  int tmp_algo=0;
+  int bits=0;
+  int algorithm=DNSSECKeeper::ECDSA256;
+  for(unsigned int n=1; n < cmds.size(); ++n) {
+    if (pdns_iequals(cmds.at(n), "zsk"))
+      keyOrZone = false;
+    else if (pdns_iequals(cmds.at(n), "ksk"))
+      keyOrZone = true;
+    else if ((tmp_algo = DNSSECKeeper::shorthand2algorithm(cmds.at(n))) > 0) {
+      algorithm = tmp_algo;
+    }
+    else if (pdns::checked_stoi<int>(cmds.at(n)) != 0)
+      pdns::checked_stoi_into(bits, cmds.at(n));
+    else {
+      cerr << "Unknown algorithm, key flag or size '" << cmds.at(n) << "'" << endl;
       return 0;
     }
-    else if (cmds.at(1) == "create-key") {
+  }
+  cerr<<"Generating a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<endl;
+  if(bits)
+    cerr<<"Requesting specific key size of "<<bits<<" bits"<<endl;
 
-      if (cmds.size() < 4) {
-        cerr << "Usage: pdnsutil hsm create-key ZONE KEY-ID [BITS]" << endl;
-        return 1;
-      }
-      DomainInfo di;
-      DNSName zone(cmds.at(2));
-      unsigned int id;
-      int bits = 2048;
-      // verify zone
-      if (!B.getDomainInfo(zone, di)) {
-        cerr << "Unable to create key for unknown zone '" << zone << "'" << std::endl;
-        return 1;
+  shared_ptr<DNSCryptoKeyEngine> dpk(DNSCryptoKeyEngine::make(algorithm));
+  if(!bits) {
+    if(algorithm <= 10)
+      bits = keyOrZone ? 2048 : 1024;
+    else {
+      if(algorithm == DNSSECKeeper::ECCGOST || algorithm == DNSSECKeeper::ECDSA256 || algorithm == DNSSECKeeper::ED25519)
+        bits = 256;
+      else if(algorithm == DNSSECKeeper::ECDSA384)
+        bits = 384;
+      else if(algorithm == DNSSECKeeper::ED448)
+        bits = 456;
+      else {
+        throw runtime_error("Can not guess key size for algorithm "+std::to_string(algorithm));
       }
+    }
+  }
+  dpk->create(bits);
+  DNSSECPrivateKey dspk;
+  dspk.setKey(dpk, keyOrZone ? 257 : 256, algorithm);
 
-      pdns::checked_stoi_into(id, cmds.at(3));
-      std::vector<DNSBackend::KeyData> keys;
-      if (!B.getDomainKeys(zone, keys)) {
-        cerr << "No keys found for zone " << zone << std::endl;
-        return 1;
-      }
+  // print key to stdout
+  cout << "Flags: " << dspk.getFlags() << endl <<
+           dspk.getKey()->convertToISC() << endl;
+  return 0;
+}
 
-      std::unique_ptr<DNSCryptoKeyEngine> dke = nullptr;
-      // lookup correct key
-      for(DNSBackend::KeyData &kd :  keys) {
-        if (kd.id == id) {
-          // found our key.
-          DNSKEYRecordContent dkrc;
-          dke = DNSCryptoKeyEngine::makeFromISCString(dkrc, kd.content);
-        }
-      }
+static int generateTSIGKey(vector<string>& cmds)
+{
+  string usage = "Syntax: " + cmds.at(0) + " name (hmac-md5|hmac-sha1|hmac-sha224|hmac-sha256|hmac-sha384|hmac-sha512)";
+  if (cmds.size() < 3) {
+    cerr << usage << endl;
+    return 0;
+  }
+  DNSName name(cmds.at(1));
+  DNSName algo(cmds.at(2));
+  string key;
+  try {
+    key = makeTSIGKey(algo);
+  } catch(const PDNSException& e) {
+    cerr << "Could not create new TSIG key " << name << " " << algo << ": "<< e.reason << endl;
+    return 1;
+  }
 
-      if (!dke) {
-        cerr << "Could not find key with ID " << id << endl;
-        return 1;
-      }
-      if (cmds.size() > 4) {
-        pdns::checked_stoi_into(bits, cmds.at(4));
-      }
-      if (bits < 1) {
-        cerr << "Invalid bit size " << bits << "given, must be positive integer";
-        return 1;
-      }
-      try {
-        dke->create(bits);
-      } catch (PDNSException& e) {
-         cerr << e.reason << endl;
-         return 1;
-      }
+  UeberBackend B("default");
+  if (B.setTSIGKey(name, DNSName(algo), key)) { // you are feeling bored, put up DNSName(algo) up earlier
+    cout << "Create new TSIG key " << name << " " << algo << " " << key << endl;
+  } else {
+    cerr << "Failure storing new TSIG key " << name << " " << algo << " " << key << endl;
+    return 1;
+  }
+  return 0;
+}
 
-      cerr << "Key of size " << dke->getBits() << " created" << std::endl;
-      return 0;
-    }
-#else
-    cerr<<"PKCS#11 support not enabled"<<endl;
+static int importTSIGKey(vector<string>& cmds)
+{
+  if (cmds.size() < 4) {
+    cerr << "Syntax: " << cmds.at(0) << " name algorithm key" << endl;
+    return 0;
+  }
+  DNSName name(cmds.at(1));
+  string algo = cmds.at(2);
+  string key = cmds.at(3);
+
+  UeberBackend B("default");
+  if (B.setTSIGKey(name, DNSName(algo), key)) {
+    cout << "Imported TSIG key " << name << " " << algo << endl;
+  }
+  else {
+    cerr << "Failure importing TSIG key " << name << " " << algo << endl;
     return 1;
-#endif
   }
-  else if (cmds.at(0) == "b2b-migrate") {
-    if (cmds.size() < 3) {
-      cerr << "Usage: b2b-migrate OLD NEW" << endl;
-      return 1;
+  return 0;
+}
+
+static int deleteTSIGKey(vector<string>& cmds)
+{
+  if (cmds.size() < 2) {
+    cerr << "Syntax: " << cmds.at(0) << " name" << endl;
+    return 0;
+  }
+  DNSName name(cmds.at(1));
+
+  UeberBackend B("default");
+  if (B.deleteTSIGKey(name)) {
+    cout << "Deleted TSIG key " << name << endl;
+  }
+  else {
+    cerr << "Failure deleting TSIG key " << name << endl;
+    return 1;
+  }
+  return 0;
+}
+
+static int listTSIGKeys([[maybe_unused]] vector<string>& cmds)
+{
+  std::vector<struct TSIGKey> keys;
+  UeberBackend B("default");
+  if (B.getTSIGKeys(keys)) {
+    for (const TSIGKey& key : keys) {
+      cout << key.name.toString() << " " << key.algorithm.toString() << " " << key.key << endl;
     }
+  }
+  return 0;
+}
 
-    if (cmds.at(1) == cmds.at(2)) {
-      cerr << "Error: b2b-migrate OLD NEW: OLD cannot be the same as NEW" << endl;
-      return 1;
+static int activateTSIGKey(vector<string>& cmds)
+{
+  string metaKey;
+  if (cmds.size() < 4) {
+    cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary}" << endl;
+    return 0;
+  }
+  DNSName zname(cmds.at(1));
+  string name = cmds.at(2);
+  if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
+    metaKey = "TSIG-ALLOW-AXFR";
+  else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
+    metaKey = "AXFR-MASTER-TSIG";
+  else {
+    cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
+    return 1;
+  }
+  UeberBackend B("default");
+  DomainInfo di;
+  if (!B.getDomainInfo(zname, di)) {
+    cerr << "Zone '" << zname << "' does not exist" << endl;
+    return 1;
+  }
+  std::vector<std::string> meta;
+  if (!B.getDomainMetadata(zname, metaKey, meta)) {
+    cerr << "Failure enabling TSIG key " << name << " for " << zname << endl;
+    return 1;
+  }
+  bool found = false;
+  for (const std::string& tmpname : meta) {
+    if (tmpname == name) {
+      found = true;
+      break;
     }
+  }
+  if (!found)
+    meta.push_back(name);
+  if (B.setDomainMetadata(zname, metaKey, meta)) {
+    cout << "Enabled TSIG key " << name << " for " << zname << endl;
+  }
+  else {
+    cerr << "Failure enabling TSIG key " << name << " for " << zname << endl;
+    return 1;
+  }
+  return 0;
+}
 
-    unique_ptr<DNSBackend> src{nullptr};
-    unique_ptr<DNSBackend> tgt{nullptr};
+static int deactivateTSIGKey(vector<string>& cmds)
+{
+  string metaKey;
+  if (cmds.size() < 4) {
+    cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary|producer|consumer}" << endl;
+    return 0;
+  }
+  DNSName zname(cmds.at(1));
+  string name = cmds.at(2);
+  if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
+    metaKey = "TSIG-ALLOW-AXFR";
+  else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
+    metaKey = "AXFR-MASTER-TSIG";
+  else {
+    cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
+    return 1;
+  }
 
-    for (auto& backend : BackendMakers().all()) {
-      if (backend->getPrefix() == cmds.at(1)) {
-         src = std::move(backend);
-      }
-      else if (backend->getPrefix() == cmds.at(2)) {
-         tgt = std::move(backend);
+  UeberBackend B("default");
+  DomainInfo di;
+  if (!B.getDomainInfo(zname, di)) {
+    cerr << "Zone '" << zname << "' does not exist" << endl;
+    return 1;
+  }
+  std::vector<std::string> meta;
+  if (!B.getDomainMetadata(zname, metaKey, meta)) {
+    cerr << "Failure disabling TSIG key " << name << " for " << zname << endl;
+    return 1;
+  }
+  std::vector<std::string>::iterator iter = meta.begin();
+  for (; iter != meta.end(); ++iter)
+    if (*iter == name)
+      break;
+  if (iter != meta.end())
+    meta.erase(iter);
+  if (B.setDomainMetadata(zname, metaKey, meta)) {
+    cout << "Disabled TSIG key " << name << " for " << zname << endl;
+  }
+  else {
+    cerr << "Failure disabling TSIG key " << name << " for " << zname << endl;
+    return 1;
+  }
+  return 0;
+}
+
+static int getMeta(vector<string>& cmds)
+{
+  UeberBackend B("default");
+  if (cmds.size() < 2) {
+    cerr << "Syntax: " << cmds.at(0) << " zone [kind kind ..]" << endl;
+    return 1;
+  }
+  DNSName zone(cmds.at(1));
+  vector<string> keys;
+
+  DomainInfo di;
+  if (!B.getDomainInfo(zone, di)) {
+     cerr << "Invalid zone '" << zone << "'" << endl;
+     return 1;
+  }
+
+  if (cmds.size() > 2) {
+    keys.assign(cmds.begin() + 2, cmds.end());
+    std::cout << "Metadata for '" << zone << "'" << endl;
+    for(const auto& kind :  keys) {
+      vector<string> meta;
+      meta.clear();
+      if (B.getDomainMetadata(zone, kind, meta)) {
+        cout << kind << " = " << boost::join(meta, ", ") << endl;
       }
     }
-
-    if (src == nullptr) {
-      cerr << "Unknown source backend '" << cmds.at(1) << "'" << endl;
-      return 1;
+  } else {
+    std::map<std::string, std::vector<std::string> > meta;
+    std::cout << "Metadata for '" << zone << "'" << endl;
+    B.getAllDomainMetadata(zone, meta);
+    for(const auto& each_meta: meta) {
+      cout << each_meta.first << " = " << boost::join(each_meta.second, ", ") << endl;
     }
-    if (tgt == nullptr) {
-      cerr << "Unknown target backend '" << cmds.at(2) << "'" << endl;
+  }
+  return 0;
+}
+
+static int setMeta(vector<string>& cmds)
+{
+  if (cmds.size() < 3) {
+    cerr << "Syntax: " << cmds.at(0) << " ZONE KIND [VALUE VALUE ..]" << endl;
+    return 1;
+  }
+  DNSName zone(cmds.at(1));
+  string kind = cmds.at(2);
+  const static std::array<string, 7> multiMetaWhitelist = {"ALLOW-AXFR-FROM", "ALLOW-DNSUPDATE-FROM",
+    "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE", "GSS-ALLOW-AXFR-PRINCIPAL",
+    "PUBLISH-CDS"};
+  bool clobber = true;
+  if (cmds.at(0) == "add-meta") {
+    clobber = false;
+    if (find(multiMetaWhitelist.begin(), multiMetaWhitelist.end(), kind) == multiMetaWhitelist.end() && kind.find("X-") != 0) {
+      cerr<<"Refusing to add metadata to single-value metadata "<<kind<<endl;
       return 1;
     }
+  }
+  vector<string> meta(cmds.begin() + 3, cmds.end());
+  return addOrSetMeta(zone, kind, meta, clobber);
+}
+
+#ifdef HAVE_P11KIT1 // {
+static int HSMAssign(vector<string>& cmds)
+{
+  DNSCryptoKeyEngine::storvector_t storvect;
+  DomainInfo di;
+  std::vector<DNSBackend::KeyData> keys;
+
+  if (cmds.size() < 9) {
+    std::cout << "Usage: pdnsutil hsm assign ZONE ALGORITHM {ksk|zsk} MODULE TOKEN PIN LABEL (PUBLABEL)" << std::endl;
+    return 1;
+  }
+
+  UeberBackend B("default");
+  DNSName zone(cmds.at(2));
+
+  // verify zone
+  if (!B.getDomainInfo(zone, di)) {
+    cerr << "Unable to assign module to unknown zone '" << zone << "'" << std::endl;
+    return 1;
+  }
 
-    cout<<"Moving zone(s) from "<<src->getPrefix()<<" to "<<tgt->getPrefix()<<endl;
+  int algorithm = DNSSECKeeper::shorthand2algorithm(cmds.at(3));
+  if (algorithm<0) {
+    cerr << "Unable to use unknown algorithm '" << cmds.at(3) << "'" << std::endl;
+    return 1;
+  }
 
-    vector<DomainInfo> domains;
+  bool keyOrZone = (cmds.at(4) == "ksk" ? true : false);
+  string module = cmds.at(5);
+  string slot = cmds.at(6);
+  string pin = cmds.at(7);
+  string label = cmds.at(8);
+  string pub_label;
+  if (cmds.size() > 9)
+    pub_label = cmds.at(9);
+  else
+     pub_label = label;
 
-    tgt->getAllDomains(&domains, false, true);
-    if (!domains.empty())
-      throw PDNSException("Target backend has zone(s), please clean it first");
+  std::ostringstream iscString;
+  iscString << "Private-key-format: v1.2" << std::endl <<
+    "Algorithm: " << algorithm << std::endl <<
+    "Engine: " << module << std::endl <<
+    "Slot: " << slot << std::endl <<
+    "PIN: " << pin << std::endl <<
+    "Label: " << label << std::endl <<
+    "PubLabel: " << pub_label << std::endl;
 
-    src->getAllDomains(&domains, false, true);
-    // iterate zones
-    for(const DomainInfo& di: domains) {
-      size_t nr,nc,nm,nk;
-      DomainInfo di_new;
-      DNSResourceRecord rr;
-      cout<<"Processing '"<<di.zone<<"'"<<endl;
-      // create zone
-      if (!tgt->createDomain(di.zone, di.kind, di.primaries, di.account))
-         throw PDNSException("Failed to create zone");
-      if (!tgt->getDomainInfo(di.zone, di_new)) throw PDNSException("Failed to create zone");
-      // move records
-      if (!src->list(di.zone, di.id, true)) throw PDNSException("Failed to list records");
-      nr=0;
+  DNSKEYRecordContent drc;
 
-      tgt->startTransaction(di.zone, di_new.id);
+  shared_ptr<DNSCryptoKeyEngine> dke(DNSCryptoKeyEngine::makeFromISCString(drc, iscString.str()));
+  if(!dke->checkKey()) {
+    cerr << "Invalid DNS Private Key in engine " << module << " slot " << slot << std::endl;
+    return 1;
+  }
+  DNSSECPrivateKey dpk;
+  dpk.setKey(dke, keyOrZone ? 257 : 256);
 
-      while(src->get(rr)) {
-        rr.domain_id = di_new.id;
-        if (!tgt->feedRecord(rr, DNSName())) throw PDNSException("Failed to feed record");
-        nr++;
-      }
+  // make sure this key isn't being reused.
+  B.getDomainKeys(zone, keys);
 
-      // move comments
-      nc=0;
-      if (src->listComments(di.id)) {
-        Comment c;
-        while(src->getComment(c)) {
-          c.domain_id = di_new.id;
-          if (!tgt->feedComment(c)) {
-            throw PDNSException("Target backend does not support comments - remove them first");
-          }
-          nc++;
-        }
-      }
-      // move metadata
-      nm=0;
-      std::map<std::string, std::vector<std::string> > meta;
-      if (src->getAllDomainMetadata(di.zone, meta)) {
-        for (const auto& i : meta) {
-          if (!tgt->setDomainMetadata(di.zone, i.first, i.second))
-            throw PDNSException("Failed to feed zone metadata");
-          nm++;
-        }
-      }
-      // move keys
-      nk=0;
-      // temp var for KeyID
-      int64_t keyID;
-      std::vector<DNSBackend::KeyData> keys;
-      if (src->getDomainKeys(di.zone, keys)) {
-        for(const DNSBackend::KeyData& k: keys) {
-          tgt->addDomainKey(di.zone, k, keyID);
-          nk++;
-        }
-      }
-      tgt->commitTransaction();
-      cout<<"Moved "<<nr<<" record(s), "<<nc<<" comment(s), "<<nm<<" metadata(s) and "<<nk<<" cryptokey(s)"<<endl;
+  int64_t id{-1};
+  for(DNSBackend::KeyData& kd :  keys) {
+    if (kd.content == iscString.str()) {
+      // it's this one, I guess...
+      id = kd.id;
+      break;
     }
+  }
 
-    int ntk=0;
-    // move tsig keys
-    std::vector<struct TSIGKey> tkeys;
-    if (src->getTSIGKeys(tkeys)) {
-      for(auto& tk: tkeys) {
-        if (!tgt->setTSIGKey(tk.name, tk.algorithm, tk.key)) throw PDNSException("Failed to feed TSIG key");
-        ntk++;
-      }
-    }
-    cout<<"Moved "<<ntk<<" TSIG key(s)"<<endl;
+  if (id > -1) {
+    cerr << "You have already assigned this key with ID=" << id << std::endl;
+    return 1;
+  }
+
+  DNSSECKeeper dk;
+  if (!dk.addKey(zone, dpk, id)) {
+    cerr << "Unable to assign module slot to zone" << std::endl;
+    return 1;
+  }
 
-    cout<<"Remember to drop the old backend and run rectify-all-zones"<<endl;
+  cerr << "Module " << module << " slot " << slot << " assigned to " << zone << " with key id " << id << endl;
 
-    return 0;
+  return 0;
+}
+
+static int HSMCreateKey(vector<string>& cmds)
+{
+  if (cmds.size() < 4) {
+    cerr << "Usage: pdnsutil hsm create-key ZONE KEY-ID [BITS]" << endl;
+    return 1;
+  }
+  UeberBackend B("default");
+  DomainInfo di;
+  DNSName zone(cmds.at(2));
+  unsigned int id;
+  int bits = 2048;
+  // verify zone
+  if (!B.getDomainInfo(zone, di)) {
+    cerr << "Unable to create key for unknown zone '" << zone << "'" << std::endl;
+    return 1;
   }
-  else if (cmds.at(0) == "backend-cmd") {
-    if (cmds.size() < 3) {
-      cerr<<"Usage: backend-cmd BACKEND CMD [CMD..]"<<endl;
-      return 1;
-    }
 
-    std::unique_ptr<DNSBackend> matchingBackend{nullptr};
+  pdns::checked_stoi_into(id, cmds.at(3));
+  std::vector<DNSBackend::KeyData> keys;
+  if (!B.getDomainKeys(zone, keys)) {
+    cerr << "No keys found for zone " << zone << std::endl;
+    return 1;
+  }
 
-    for (auto& backend : BackendMakers().all()) {
-      if (backend->getPrefix() == cmds.at(1)) {
-        matchingBackend = std::move(backend);
-      }
+  std::unique_ptr<DNSCryptoKeyEngine> dke = nullptr;
+  // lookup correct key
+  for(DNSBackend::KeyData &kd :  keys) {
+    if (kd.id == id) {
+      // found our key.
+      DNSKEYRecordContent dkrc;
+      dke = DNSCryptoKeyEngine::makeFromISCString(dkrc, kd.content);
     }
+  }
 
-    if (matchingBackend == nullptr) {
-      cerr << "Unknown backend '" << cmds.at(1) << "'" << endl;
-      return 1;
-    }
+  if (!dke) {
+    cerr << "Could not find key with ID " << id << endl;
+    return 1;
+  }
+  if (cmds.size() > 4) {
+    pdns::checked_stoi_into(bits, cmds.at(4));
+  }
+  if (bits < 1) {
+    cerr << "Invalid bit size " << bits << "given, must be positive integer";
+    return 1;
+  }
+  try {
+    dke->create(bits);
+  } catch (PDNSException& e) {
+     cerr << e.reason << endl;
+     return 1;
+  }
 
-    for (auto i = next(begin(cmds), 2); i != end(cmds); ++i) {
-      cerr << "== " << *i << endl;
-      cout << matchingBackend->directBackendCmd(*i);
-    }
+  cerr << "Key of size " << dke->getBits() << " created" << std::endl;
+  return 0;
+}
+#endif // }
 
+static int HSM([[maybe_unused]] vector<string>& cmds)
+{
+#ifdef HAVE_P11KIT1
+  if (cmds.size() < 2) {
+    cerr << "Missing sub-command for pdnsutil hsm"<< std::endl;
     return 0;
   }
-  else if (cmds.at(0) == "backend-lookup") {
-    if (cmds.size() < 3) {
-      cerr << "Usage: backend-lookup BACKEND NAME [TYPE [CLIENT-IP-SUBNET]]" << endl;
-      return 1;
+  else if (cmds.at(1) == "assign") {
+    return HSMAssign(cmds);
+  }
+  else if (cmds.at(1) == "create-key") {
+    return HSMCreateKey(cmds);
+  }
+  return 1;
+#else
+  cerr<<"PKCS#11 support not enabled"<<endl;
+  return 1;
+#endif
+}
+
+static int B2BMigrate(vector<string>& cmds)
+{
+  if (cmds.size() < 3) {
+    cerr << "Usage: b2b-migrate OLD NEW" << endl;
+    return 1;
+  }
+
+  if (cmds.at(1) == cmds.at(2)) {
+    cerr << "Error: b2b-migrate OLD NEW: OLD cannot be the same as NEW" << endl;
+    return 1;
+  }
+
+  unique_ptr<DNSBackend> src{nullptr};
+  unique_ptr<DNSBackend> tgt{nullptr};
+
+  for (auto& backend : BackendMakers().all()) {
+    if (backend->getPrefix() == cmds.at(1)) {
+       src = std::move(backend);
     }
+    else if (backend->getPrefix() == cmds.at(2)) {
+       tgt = std::move(backend);
+    }
+  }
 
-    std::unique_ptr<DNSBackend> matchingBackend{nullptr};
+  if (src == nullptr) {
+    cerr << "Unknown source backend '" << cmds.at(1) << "'" << endl;
+    return 1;
+  }
+  if (tgt == nullptr) {
+    cerr << "Unknown target backend '" << cmds.at(2) << "'" << endl;
+    return 1;
+  }
+
+  cout<<"Moving zone(s) from "<<src->getPrefix()<<" to "<<tgt->getPrefix()<<endl;
+
+  vector<DomainInfo> domains;
+
+  tgt->getAllDomains(&domains, false, true);
+  if (!domains.empty())
+    throw PDNSException("Target backend has zone(s), please clean it first");
 
-    for (auto& backend : BackendMakers().all()) {
-      if (backend->getPrefix() == cmds.at(1)) {
-        matchingBackend = std::move(backend);
+  src->getAllDomains(&domains, false, true);
+  // iterate zones
+  for(const DomainInfo& di: domains) {
+    size_t nr,nc,nm,nk;
+    DomainInfo di_new;
+    DNSResourceRecord rr;
+    cout<<"Processing '"<<di.zone<<"'"<<endl;
+    // create zone
+    if (!tgt->createDomain(di.zone, di.kind, di.primaries, di.account))
+       throw PDNSException("Failed to create zone");
+    if (!tgt->getDomainInfo(di.zone, di_new)) throw PDNSException("Failed to create zone");
+    // move records
+    if (!src->list(di.zone, di.id, true)) throw PDNSException("Failed to list records");
+    nr=0;
+
+    tgt->startTransaction(di.zone, di_new.id);
+
+    while(src->get(rr)) {
+      rr.domain_id = di_new.id;
+      if (!tgt->feedRecord(rr, DNSName())) throw PDNSException("Failed to feed record");
+      nr++;
+    }
+
+    // move comments
+    nc=0;
+    if (src->listComments(di.id)) {
+      Comment c;
+      while(src->getComment(c)) {
+        c.domain_id = di_new.id;
+        if (!tgt->feedComment(c)) {
+          throw PDNSException("Target backend does not support comments - remove them first");
+        }
+        nc++;
       }
     }
-
-    if (matchingBackend == nullptr) {
-      cerr << "Unknown backend '" << cmds.at(1) << "'" << endl;
-      return 1;
+    // move metadata
+    nm=0;
+    std::map<std::string, std::vector<std::string> > meta;
+    if (src->getAllDomainMetadata(di.zone, meta)) {
+      for (const auto& i : meta) {
+        if (!tgt->setDomainMetadata(di.zone, i.first, i.second))
+          throw PDNSException("Failed to feed zone metadata");
+        nm++;
+      }
+    }
+    // move keys
+    nk=0;
+    // temp var for KeyID
+    int64_t keyID;
+    std::vector<DNSBackend::KeyData> keys;
+    if (src->getDomainKeys(di.zone, keys)) {
+      for(const DNSBackend::KeyData& k: keys) {
+        tgt->addDomainKey(di.zone, k, keyID);
+        nk++;
+      }
     }
+    tgt->commitTransaction();
+    cout<<"Moved "<<nr<<" record(s), "<<nc<<" comment(s), "<<nm<<" metadata(s) and "<<nk<<" cryptokey(s)"<<endl;
+  }
 
-    QType type = QType::ANY;
-    if (cmds.size() > 3) {
-      type = DNSRecordContent::TypeToNumber(cmds.at(3));
+  int ntk=0;
+  // move tsig keys
+  std::vector<struct TSIGKey> tkeys;
+  if (src->getTSIGKeys(tkeys)) {
+    for(auto& tk: tkeys) {
+      if (!tgt->setTSIGKey(tk.name, tk.algorithm, tk.key)) throw PDNSException("Failed to feed TSIG key");
+      ntk++;
     }
+  }
+  cout<<"Moved "<<ntk<<" TSIG key(s)"<<endl;
 
-    DNSName name{cmds.at(2)};
+  cout<<"Remember to drop the old backend and run rectify-all-zones"<<endl;
 
-    DNSPacket queryPacket(true);
-    Netmask clientNetmask;
-    if (cmds.size() > 4) {
-      clientNetmask = cmds.at(4);
-      queryPacket.setRealRemote(clientNetmask);
-    }
+  return 0;
+}
 
-    matchingBackend->lookup(type, name, -1, &queryPacket);
+static int backendCmd(vector<string>& cmds)
+{
+  if (cmds.size() < 3) {
+    cerr<<"Usage: backend-cmd BACKEND CMD [CMD..]"<<endl;
+    return 1;
+  }
 
-    bool found = false;
-    DNSZoneRecord resultZoneRecord;
-    while (matchingBackend->get(resultZoneRecord)) {
-      cout << resultZoneRecord.dr.d_name.toString() << "\t" << std::to_string(resultZoneRecord.dr.d_ttl) << "\t" << QClass(resultZoneRecord.dr.d_class).toString() << "\t" << DNSRecordContent::NumberToType(resultZoneRecord.dr.d_type, resultZoneRecord.dr.d_class) << "\t" << resultZoneRecord.dr.getContent()->getZoneRepresentation();
-      if (resultZoneRecord.scopeMask > 0) {
-        clientNetmask.setBits(resultZoneRecord.scopeMask);
-        cout << "\t" << "; " << clientNetmask.toString();
-      }
-      cout << endl;
-      found = true;
+  std::unique_ptr<DNSBackend> matchingBackend{nullptr};
+
+  for (auto& backend : BackendMakers().all()) {
+    if (backend->getPrefix() == cmds.at(1)) {
+      matchingBackend = std::move(backend);
     }
-    if (!found) {
-      cerr << "Backend found 0 zone record results";
-      if (type != QType::ANY) {
-        cerr << "- maybe retry with type ANY?";
-      }
-      cerr << endl;
-      return 1;
+  }
+
+  if (matchingBackend == nullptr) {
+    cerr << "Unknown backend '" << cmds.at(1) << "'" << endl;
+    return 1;
+  }
+
+  for (auto i = next(begin(cmds), 2); i != end(cmds); ++i) {
+    cerr << "== " << *i << endl;
+    cout << matchingBackend->directBackendCmd(*i);
+  }
+
+  return 0;
+}
+
+static int backendLookup(vector<string>& cmds)
+{
+  if (cmds.size() < 3) {
+    cerr << "Usage: backend-lookup BACKEND NAME [TYPE [CLIENT-IP-SUBNET]]" << endl;
+    return 1;
+  }
+
+  std::unique_ptr<DNSBackend> matchingBackend{nullptr};
+
+  for (auto& backend : BackendMakers().all()) {
+    if (backend->getPrefix() == cmds.at(1)) {
+      matchingBackend = std::move(backend);
     }
+  }
 
-    return 0;
+  if (matchingBackend == nullptr) {
+    cerr << "Unknown backend '" << cmds.at(1) << "'" << endl;
+    return 1;
+  }
+
+  QType type = QType::ANY;
+  if (cmds.size() > 3) {
+    type = DNSRecordContent::TypeToNumber(cmds.at(3));
+  }
+
+  DNSName name{cmds.at(2)};
+
+  DNSPacket queryPacket(true);
+  Netmask clientNetmask;
+  if (cmds.size() > 4) {
+    clientNetmask = cmds.at(4);
+    queryPacket.setRealRemote(clientNetmask);
+  }
+
+  matchingBackend->lookup(type, name, -1, &queryPacket);
+
+  bool found = false;
+  DNSZoneRecord resultZoneRecord;
+  while (matchingBackend->get(resultZoneRecord)) {
+    cout << resultZoneRecord.dr.d_name.toString() << "\t" << std::to_string(resultZoneRecord.dr.d_ttl) << "\t" << QClass(resultZoneRecord.dr.d_class).toString() << "\t" << DNSRecordContent::NumberToType(resultZoneRecord.dr.d_type, resultZoneRecord.dr.d_class) << "\t" << resultZoneRecord.dr.getContent()->getZoneRepresentation();
+    if (resultZoneRecord.scopeMask > 0) {
+      clientNetmask.setBits(resultZoneRecord.scopeMask);
+      cout << "\t" << "; " << clientNetmask.toString();
+    }
+    cout << endl;
+    found = true;
+  }
+  if (!found) {
+    cerr << "Backend found 0 zone record results";
+    if (type != QType::ANY) {
+      cerr << "- maybe retry with type ANY?";
+    }
+    cerr << endl;
+    return 1;
+  }
+
+  return 0;
+}
+
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Clean this function up.
+int main(int argc, char** argv)
+try
+{
+  po::options_description desc("Allowed options");
+  desc.add_options()
+    ("help,h", "produce help message")
+    ("version", "show version")
+    ("verbose,v", "be verbose")
+    ("force", "force an action")
+    ("config-name", po::value<string>()->default_value(""), "virtual configuration name")
+    ("config-dir", po::value<string>()->default_value(SYSCONFDIR), "location of pdns.conf")
+    ("no-colors", "do not use colors in output")
+    ("commands", po::value<vector<string> >());
+
+  po::positional_options_description p;
+  p.add("commands", -1);
+  po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), g_vm);
+  po::notify(g_vm);
+
+  vector<string> cmds;
+
+  if(g_vm.count("commands"))
+    cmds = g_vm["commands"].as<vector<string> >();
+
+  g_verbose = g_vm.count("verbose");
+
+  if (g_vm.count("version")) {
+    cout<<"pdnsutil "<<VERSION<<endl;
+    return 0;
+  }
+
+  if (cmds.empty() || g_vm.count("help") || cmds.at(0) == "help") {
+    cout << "Usage: \npdnsutil [options] <command> [params ..]\n"
+         << endl;
+    cout << "Commands:" << endl;
+    cout << "activate-tsig-key ZONE NAME {primary|secondary|producer|consumer}" << endl;
+    cout << "                                   Enable TSIG authenticated AXFR using the key NAME for ZONE" << endl;
+    cout << "activate-zone-key ZONE KEY-ID      Activate the key with key id KEY-ID in ZONE" << endl;
+    cout << "add-record ZONE NAME TYPE [ttl] content" << endl;
+    cout << "             [content..]           Add one or more records to ZONE" << endl;
+    cout << "add-autoprimary IP NAMESERVER [account]" << endl;
+    cout << "                                   Add a new autoprimary " << endl;
+    cout << "remove-autoprimary IP NAMESERVER   Remove an autoprimary" << endl;
+    cout << "list-autoprimaries                 List all autoprimaries" << endl;
+    cout << "add-zone-key ZONE {zsk|ksk} [BITS] [active|inactive] [published|unpublished]" << endl;
+    cout << "             [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO_ED25519)
+    cout << "|ed25519";
+#endif
+#if defined(HAVE_LIBCRYPTO_ED448)
+    cout << "|ed448";
+#endif
+    cout << "]" << endl;
+    cout << "                                   Add a ZSK or KSK to zone and specify algo&bits" << endl;
+    cout << "backend-cmd BACKEND CMD [CMD..]    Perform one or more backend commands" << endl;
+    cout << "backend-lookup BACKEND NAME [[TYPE] CLIENT-IP-SUBNET]" << endl;
+    cout << "                                   Perform a backend lookup of NAME, TYPE and CLIENT-IP-SUBNET" << endl;
+    cout << "b2b-migrate OLD NEW                Move all data from one backend to another" << endl;
+    cout << "bench-db [filename]                Bench database backend with queries, one zone per line" << endl;
+    cout << "check-zone ZONE                    Check a zone for correctness" << endl;
+    cout << "check-all-zones [exit-on-error]    Check all zones for correctness. Set exit-on-error to exit immediately" << endl;
+    cout << "                                   after finding an error in a zone." << endl;
+    cout << "clear-zone ZONE                    Clear all records of a zone, but keep everything else" << endl;
+    cout << "create-bind-db FNAME               Create DNSSEC db for BIND backend (bind-dnssec-db)" << endl;
+    cout << "create-secondary-zone ZONE primary-ip [primary-ip..]" << endl;
+    cout << "                                   Create secondary zone ZONE with primary IP address primary-ip" << endl;
+    cout << "change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl;
+    cout << "                                   Change secondary zone ZONE primary IP address to primary-ip" << endl;
+    cout << "create-zone ZONE [nsname]          Create empty zone ZONE" << endl;
+    cout << "deactivate-tsig-key ZONE NAME {primary|secondary}" << endl;
+    cout << "                                   Disable TSIG authenticated AXFR using the key NAME for ZONE" << endl;
+    cout << "deactivate-zone-key ZONE KEY-ID    Deactivate the key with key id KEY-ID in ZONE" << endl;
+    cout << "delete-rrset ZONE NAME TYPE        Delete named RRSET from zone" << endl;
+    cout << "delete-tsig-key NAME               Delete TSIG key (warning! will not unmap key!)" << endl;
+    cout << "delete-zone ZONE                   Delete the zone" << endl;
+    cout << "disable-dnssec ZONE                Deactivate all keys and unset PRESIGNED in ZONE" << endl;
+    cout << "edit-zone ZONE                     Edit zone contents using $EDITOR" << endl;
+    cout << "export-zone-dnskey ZONE KEY-ID     Export to stdout the public DNSKEY described" << endl;
+    cout << "export-zone-ds ZONE                Export to stdout all KSK DS records for ZONE" << endl;
+    cout << "export-zone-key ZONE KEY-ID        Export to stdout the private key described" << endl;
+    cout << "export-zone-key-pem ZONE KEY-ID    Export to stdout in PEM the private key described" << endl;
+    cout << "generate-tsig-key NAME ALGORITHM   Generate new TSIG key" << endl;
+    cout << "generate-zone-key {zsk|ksk} [ALGORITHM] [BITS]" << endl;
+    cout << "                                   Generate a ZSK or KSK to stdout with specified ALGORITHM and BITS" << endl;
+    cout << "get-meta ZONE [KIND ...]           Get zone metadata. If no KIND given, lists all known" << endl;
+    cout << "hash-password [WORK FACTOR]        Ask for a plaintext password or api key and output a hashed and salted version" << endl;
+    cout << "hash-zone-record ZONE RNAME        Calculate the NSEC3 hash for RNAME in ZONE" << endl;
+#ifdef HAVE_P11KIT1
+    cout << "hsm assign ZONE ALGORITHM {ksk|zsk} MODULE SLOT PIN LABEL" << endl <<
+            "                                   Assign a hardware signing module to a ZONE" << endl;
+    cout << "hsm create-key ZONE KEY-ID [BITS]  Create a key using hardware signing module for ZONE (use assign first)" << endl;
+    cout << "                                   BITS defaults to 2048" << endl;
+#endif
+    cout << "increase-serial ZONE               Increases the SOA-serial by 1. Uses SOA-EDIT" << endl;
+    cout << "import-tsig-key NAME ALGORITHM KEY Import TSIG key" << endl;
+    cout << "import-zone-key ZONE FILE          Import from a file a private key, ZSK or KSK" << endl;
+    cout << "       [active|inactive] [ksk|zsk] [published|unpublished] Defaults to KSK, active and published" << endl;
+    cout << "import-zone-key-pem ZONE FILE      Import a private key from a PEM file" << endl;
+    cout << "        ALGORITHM {ksk|zsk}" << endl;
+    cout << "ipdecrypt IP passphrase/key [key]  Decrypt IP address using passphrase or base64 key" << endl;
+    cout << "ipencrypt IP passphrase/key [key]  Encrypt IP address using passphrase or base64 key" << endl;
+    cout << "load-zone ZONE FILE                Load ZONE from FILE, possibly creating zone or atomically" << endl;
+    cout << "                                   replacing contents" << endl;
+    cout << "list-algorithms [with-backend]     List all DNSSEC algorithms supported, optionally also listing the crypto library used" << endl;
+    cout << "list-keys [ZONE]                   List DNSSEC keys for ZONE. When ZONE is unset, display all keys for all active zones" << endl;
+    cout << "                                   --verbose or -v will also include the keys for disabled or empty zones" << endl;
+    cout << "list-zone ZONE                     List zone contents" << endl;
+    cout << "list-all-zones [primary|secondary|native|producer|consumer]" << endl;
+    cout << "                                   List all active zone names. --verbose or -v will also include disabled or empty zones" << endl;
+    cout << "list-member-zones CATALOG          List all members of catalog zone CATALOG" << endl;
+
+    cout << "list-tsig-keys                     List all TSIG keys" << endl;
+    cout << "publish-zone-key ZONE KEY-ID       Publish the zone key with key id KEY-ID in ZONE" << endl;
+    cout << "rectify-zone ZONE [ZONE ..]        Fix up DNSSEC fields (order, auth)" << endl;
+    cout << "rectify-all-zones [quiet]          Rectify all zones. Optionally quiet output with errors only" << endl;
+    cout << "remove-zone-key ZONE KEY-ID        Remove key with KEY-ID from ZONE" << endl;
+    cout << "replace-rrset ZONE NAME TYPE [ttl] Replace named RRSET from zone" << endl;
+    cout << "       content [content..]" << endl;
+    cout << "secure-all-zones [increase-serial] Secure all zones without keys" << endl;
+    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, producer, consumer)" << endl;
+    cout << "set-options-json ZONE JSON         Change the options of ZONE to JSON" << endl;
+    cout << "set-option ZONE                    Set or remove an option for ZONE Providing an empty value removes an option" << endl;
+    cout << "  [producer|consumer]" << endl;
+    cout << "  [coo|unique|group] VALUE" << endl;
+    cout << "  [VALUE ...]" << endl;
+    cout << "set-catalog ZONE CATALOG           Change the catalog of ZONE to CATALOG. Setting CATALOG to an empty "" removes ZONE from the catalog it is in" << 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;
+    cout << "set-publish-cdnskey ZONE [delete]  Enable sending CDNSKEY responses for ZONE. Add 'delete' to publish a CDNSKEY with a" << endl;
+    cout << "                                   DNSSEC delete algorithm" << endl;
+    cout << "set-publish-cds ZONE [DIGESTALGOS] Enable sending CDS responses for ZONE, using DIGESTALGOS as signature algorithms" << endl;
+    cout << "                                   DIGESTALGOS should be a comma separated list of numbers, it is '2' by default" << endl;
+    cout << "add-meta ZONE KIND VALUE           Add zone metadata, this adds to the existing KIND" << endl;
+    cout << "                   [VALUE ...]" << endl;
+    cout << "set-meta ZONE KIND [VALUE] [VALUE] Set zone metadata, optionally providing a value. *No* value clears meta" << endl;
+    cout << "                                   Note - this will replace all metadata records of KIND!" << endl;
+    cout << "show-zone ZONE                     Show DNSSEC (public) key details about a zone" << endl;
+    cout << "unpublish-zone-key ZONE KEY-ID     Unpublish the zone key with key id KEY-ID in ZONE" << endl;
+    cout << "unset-nsec3 ZONE                   Switch back to NSEC" << endl;
+    cout << "unset-presigned ZONE               No longer use presigned RRSIGs" << endl;
+    cout << "unset-publish-cdnskey ZONE         Disable sending CDNSKEY responses for ZONE" << endl;
+    cout << "unset-publish-cds ZONE             Disable sending CDS responses for ZONE" << endl;
+    cout << "test-schema ZONE                   Test DB schema - will create ZONE" << endl;
+    cout << "raw-lua-from-content TYPE CONTENT  Display record contents in a form suitable for dnsdist's `SpoofRawAction`" << endl;
+    cout << "zonemd-verify-file ZONE FILE       Validate ZONEMD for ZONE" << endl;
+    cout << "lmdb-get-backend-version           Get schema version supported by backend" << endl;
+    cout << desc << endl;
+
+    return 0;
+  }
+
+  loadMainConfig(g_vm["config-dir"].as<string>());
+
+  if (cmds.at(0) == "lmdb-get-backend-version") {
+    return lmdbGetBackendVersion(cmds);
+  }
+  if (cmds.at(0) == "test-algorithm") {
+    return testAlgorithm(cmds);
+  }
+
+  if (cmds.at(0) == "ipencrypt" || cmds.at(0) == "ipdecrypt") {
+    return ipEncrypt(cmds);
+  }
+
+  if (cmds.at(0) == "test-algorithms") {
+    return testAlgorithms(cmds);
+  }
+
+  if (cmds.at(0) == "list-algorithms") {
+    return listAlgorithms(cmds);
+  }
+
+  reportAllTypes();
+
+  if (cmds.at(0) == "create-bind-db") {
+    return createBindDb(cmds);
+  }
+
+  if (cmds.at(0) == "raw-lua-from-content") {
+    return rawLuaFromContent(cmds);
+  }
+  else if (cmds.at(0) == "hash-password") {
+    return hashPassword(cmds);
+  }
+
+  if(cmds[0] == "zonemd-verify-file") {
+    return zonemdVerifyFile(cmds);
+  }
+
+  if (cmds.at(0) == "test-schema") {
+    return testSchema(cmds);
+  }
+  if (cmds.at(0) == "rectify-zone") {
+    return rectifyZone(cmds);
+  }
+  else if (cmds.at(0) == "rectify-all-zones") {
+    return rectifyAllZones(cmds);
+  }
+  else if (cmds.at(0) == "check-zone") {
+    return checkZone(cmds);
+  }
+  else if (cmds.at(0) == "bench-db") {
+    return benchDb(cmds);
+  }
+  else if (cmds.at(0) == "check-all-zones") {
+    return checkAllZones(cmds);
+  }
+  else if (cmds.at(0) == "list-all-zones") {
+    return listAllZones(cmds);
+  }
+  else if (cmds.at(0) == "list-member-zones") {
+    return listMemberZones(cmds);
+  }
+  else if (cmds.at(0) == "test-zone") {
+    return testZone(cmds);
+  }
+  else if (cmds.at(0) == "test-all-zones") {
+    return testAllZones(cmds);
+  }
+  else if (cmds.at(0) == "test-speed") {
+    return testSpeed(cmds);
+  }
+  else if (cmds.at(0) == "verify-crypto") {
+    return verifyCrypto(cmds);
+  }
+  else if (cmds.at(0) == "show-zone") {
+    return showZone(cmds);
+  }
+  else if (cmds.at(0) == "export-zone-ds") {
+    return exportZoneDS(cmds);
+  }
+  else if (cmds.at(0) == "disable-dnssec") {
+    return disableDNSSEC(cmds);
+  }
+  else if (cmds.at(0) == "activate-zone-key") {
+    return activateZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "deactivate-zone-key") {
+    return deactivateZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "publish-zone-key") {
+    return publishZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "unpublish-zone-key") {
+    return unpublishZoneKey(cmds);
+  }
+
+  else if (cmds.at(0) == "add-zone-key") {
+    return addZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "remove-zone-key") {
+    return removeZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "delete-zone") {
+    return deleteZone(cmds);
+  }
+  else if (cmds.at(0) == "create-zone") {
+    return createZone(cmds);
+  }
+  else if (cmds.at(0) == "create-secondary-zone") {
+    return createSecondaryZone(cmds);
+  }
+  else if (cmds.at(0) == "change-secondary-zone-primary") {
+    return changeSecondaryZonePrimary(cmds);
+  }
+  else if (cmds.at(0) == "add-record") {
+    return addRecord(cmds);
+  }
+  else if (cmds.at(0) == "add-autoprimary") {
+    return addAutoprimary(cmds);
+  }
+  else if (cmds.at(0) == "remove-autoprimary") {
+    return removeAutoprimary(cmds);
+  }
+  else if (cmds.at(0) == "list-autoprimaries") {
+    return listAutoprimaries(cmds);
+  }
+  else if (cmds.at(0) == "replace-rrset") {
+    return replaceRRSet(cmds);
+  }
+  else if (cmds.at(0) == "delete-rrset") {
+    return deleteRRSet(cmds);
+  }
+  else if (cmds.at(0) == "list-zone") {
+    return listZone(cmds);
+  }
+  else if (cmds.at(0) == "edit-zone") {
+    return editZone(cmds);
+  }
+  else if (cmds.at(0) == "clear-zone") {
+    return clearZone(cmds);
+  }
+  else if (cmds.at(0) == "list-keys") {
+    return listKeys(cmds);
+  }
+  else if (cmds.at(0) == "load-zone") {
+    return loadZone(cmds);
+  }
+  else if (cmds.at(0) == "secure-zone") {
+    return secureZone(cmds);
+  }
+  else if (cmds.at(0) == "secure-all-zones") {
+    return secureAllZones(cmds);
+  }
+  else if (cmds.at(0) == "set-kind") {
+    return setKind(cmds);
+  }
+  else if (cmds.at(0) == "set-options-json") {
+    return setOptionsJson(cmds);
+  }
+  else if (cmds.at(0) == "set-option") {
+    return setOption(cmds);
+  }
+  else if (cmds.at(0) == "set-catalog") {
+    return setCatalog(cmds);
+  }
+  else if (cmds.at(0) == "set-account") {
+    return setAccount(cmds);
+  }
+  else if (cmds.at(0) == "set-nsec3") {
+    return setNsec3(cmds);
+  }
+  else if (cmds.at(0) == "set-presigned") {
+    return setPresigned(cmds);
+  }
+  else if (cmds.at(0) == "set-publish-cdnskey") {
+    return setPublishCDNSKey(cmds);
+  }
+  else if (cmds.at(0) == "set-publish-cds") {
+    return setPublishCDs(cmds);
+  }
+  else if (cmds.at(0) == "unset-presigned") {
+    return unsetPresigned(cmds);
+  }
+  else if (cmds.at(0) == "unset-publish-cdnskey") {
+    return unsetPublishCDNSKey(cmds);
+  }
+  else if (cmds.at(0) == "unset-publish-cds") {
+    return unsetPublishCDs(cmds);
+  }
+  else if (cmds.at(0) == "hash-zone-record") {
+    return hashZoneRecord(cmds);
+  }
+  else if (cmds.at(0) == "unset-nsec3") {
+    return unsetNSec3(cmds);
+  }
+  else if (cmds.at(0) == "export-zone-key") {
+    return exportZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "export-zone-key-pem") {
+    return exportZoneKeyPEM(cmds);
+  }
+  else if (cmds.at(0) == "increase-serial") {
+    return increaseSerial(cmds);
+  }
+  else if (cmds.at(0) == "import-zone-key-pem") {
+    return importZoneKeyPEM(cmds);
+  }
+  else if (cmds.at(0) == "import-zone-key") {
+    return importZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "export-zone-dnskey") {
+    return expotZoneDNSKey(cmds);
+  }
+  else if (cmds.at(0) == "generate-zone-key") {
+    return generateZoneKey(cmds);
+  }
+  else if (cmds.at(0) == "generate-tsig-key") {
+    return generateTSIGKey(cmds);
+  }
+  else if (cmds.at(0) == "import-tsig-key") {
+    return importTSIGKey(cmds);
+  }
+  else if (cmds.at(0) == "delete-tsig-key") {
+    return deleteTSIGKey(cmds);
+  }
+  else if (cmds.at(0) == "list-tsig-keys") {
+    return listTSIGKeys(cmds);
+  }
+  else if (cmds.at(0) == "activate-tsig-key") {
+    return activateTSIGKey(cmds);
+  }
+  else if (cmds.at(0) == "deactivate-tsig-key") {
+    return deactivateTSIGKey(cmds);
+  }
+  else if (cmds.at(0) == "get-meta") {
+    return getMeta(cmds);
+  }
+  else if (cmds.at(0) == "set-meta" || cmds.at(0) == "add-meta") {
+    return setMeta(cmds);
+  }
+  else if (cmds.at(0) == "hsm") {
+    return HSM(cmds);
+  }
+  else if (cmds.at(0) == "b2b-migrate") {
+    return B2BMigrate(cmds);
+  }
+  else if (cmds.at(0) == "backend-cmd") {
+    return backendCmd(cmds);
+  }
+  else if (cmds.at(0) == "backend-lookup") {
+    return backendLookup(cmds);
   }
   else {
     cerr << "Unknown command '" << cmds.at(0) << "'" << endl;