2 #include <boost/smart_ptr/make_shared_array.hpp>
9 #include "credentials.hh"
10 #include "dnsseckeeper.hh"
11 #include "dnssecinfra.hh"
16 #include <boost/program_options.hpp>
17 #include <boost/assign/std/vector.hpp>
18 #include <boost/assign/list_of.hpp>
20 #include "tsigutils.hh"
21 #include "dnsbackend.hh"
22 #include "ueberbackend.hh"
23 #include "arguments.hh"
24 #include "auth-packetcache.hh"
25 #include "auth-querycache.hh"
26 #include "auth-zonecache.hh"
27 #include "zoneparser-tng.hh"
28 #include "signingpipe.hh"
29 #include "dns_random.hh"
30 #include "ipcipher.hh"
37 #include <termios.h> //termios, TCSANOW, ECHO, ICANON
38 #include "opensslsigners.hh"
43 #include "ssqlite3.hh"
44 #include "bind-dnssec.schema.sqlite3.sql.h"
50 AuthZoneCache g_zoneCache
;
51 uint16_t g_maxNSEC3Iterations
{0};
53 namespace po
= boost::program_options
;
54 po::variables_map g_vm
;
56 string g_programname
="pdns";
68 static std::string
comboAddressVecToString(const std::vector
<ComboAddress
>& vec
) {
70 strs
.reserve(vec
.size());
71 for (const auto& ca
: vec
) {
72 strs
.push_back(ca
.toStringWithPortExcept(53));
74 return boost::join(strs
, ",");
77 static void loadMainConfig(const std::string
& configdir
)
79 ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=configdir
;
80 ::arg().set("default-ttl","Seconds a result is valid if not set otherwise")="3600";
81 ::arg().set("launch","Which backends to launch");
82 ::arg().set("dnssec","if we should do dnssec")="true";
83 ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")=g_vm
["config-name"].as
<string
>();
84 ::arg().setCmd("help","Provide a helpful message");
85 ::arg().set("load-modules","Load this module - supply absolute or relative path")="";
86 //::arg().laxParse(argc,argv);
88 if(::arg().mustDo("help")) {
89 cout
<<"syntax:"<<endl
<<endl
;
90 cout
<<::arg().helpstring(::arg()["help"])<<endl
;
94 if(!::arg()["config-name"].empty())
95 g_programname
+="-"+::arg()["config-name"];
97 string configname
=::arg()["config-dir"]+"/"+g_programname
+".conf";
98 cleanSlashes(configname
);
100 ::arg().set("resolver","Use this resolver for ALIAS and the internal stub resolver")="no";
101 ::arg().set("default-ksk-algorithm","Default KSK algorithm")="ecdsa256";
102 ::arg().set("default-ksk-size","Default KSK size (0 means default)")="0";
103 ::arg().set("default-zsk-algorithm","Default ZSK algorithm")="";
104 ::arg().set("default-zsk-size","Default ZSK size (0 means default)")="0";
105 ::arg().set("default-soa-edit","Default SOA-EDIT value")="";
106 ::arg().set("default-soa-edit-signed","Default SOA-EDIT value for signed zones")="";
107 ::arg().set("max-ent-entries", "Maximum number of empty non-terminals in a zone")="100000";
108 ::arg().set("module-dir","Default directory for modules")=PKGLIBDIR
;
109 ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
110 ::arg().setSwitch("query-logging","Hint backends that queries should be logged")="no";
111 ::arg().set("loglevel","Amount of logging. Higher is more.")="3";
112 ::arg().setSwitch("direct-dnskey","Fetch DNSKEY, CDS and CDNSKEY RRs from backend during DNSKEY or CDS/CDNSKEY synthesis")="no";
113 ::arg().set("max-nsec3-iterations","Limit the number of NSEC3 hash iterations")="500"; // RFC5155 10.3
114 ::arg().set("max-signature-cache-entries", "Maximum number of signatures cache entries")="";
115 ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
116 ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
117 ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
118 ::arg().setSwitch("upgrade-unknown-types","Transparently upgrade known TYPExxx records. Recommended to keep off, except for PowerDNS upgrades until data sources are cleaned up")="no";
119 ::arg().laxFile(configname
.c_str());
121 if(!::arg()["load-modules"].empty()) {
122 vector
<string
> modules
;
124 stringtok(modules
,::arg()["load-modules"], ", ");
125 if (!UeberBackend::loadModules(modules
, ::arg()["module-dir"])) {
130 g_log
.toConsole(Logger::Error
); // so we print any errors
131 BackendMakers().launch(::arg()["launch"]); // vrooooom!
132 if(::arg().asNum("loglevel") >= 3) // so you can't kill our errors
133 g_log
.toConsole((Logger::Urgency
)::arg().asNum("loglevel"));
135 //cerr<<"Backend: "<<::arg()["launch"]<<", '" << ::arg()["gmysql-dbname"] <<"'" <<endl;
137 S
.declare("qsize-q","Number of questions waiting for database attention");
139 ::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
140 ::arg().set("cache-ttl","Seconds to store packets in the PacketCache")="20";
141 ::arg().set("negquery-cache-ttl","Seconds to store negative query results in the QueryCache")="60";
142 ::arg().set("query-cache-ttl","Seconds to store query results in the QueryCache")="20";
143 ::arg().set("default-soa-content","Default SOA content")="a.misconfigured.dns.server.invalid hostmaster.@ 0 10800 3600 604800 3600";
144 ::arg().set("chroot","Switch to this chroot jail")="";
145 ::arg().set("dnssec-key-cache-ttl","Seconds to cache DNSSEC keys from the database")="30";
146 ::arg().set("domain-metadata-cache-ttl", "Seconds to cache zone metadata from the database") = "0";
147 ::arg().set("zone-metadata-cache-ttl", "Seconds to cache zone metadata from the database") = "60";
148 ::arg().set("consistent-backends", "Assume individual zones are not divided over backends. Send only ANY lookup operations to the backend to reduce the number of lookups") = "yes";
150 // Keep this line below all ::arg().set() statements
151 if (! ::arg().laxFile(configname
.c_str()))
152 cerr
<<"Warning: unable to read configuration file '"<<configname
<<"': "<<stringerror()<<endl
;
154 #ifdef HAVE_LIBSODIUM
155 if (sodium_init() == -1) {
156 cerr
<<"Unable to initialize sodium crypto library"<<endl
;
162 if (!::arg()["chroot"].empty()) {
163 if (chroot(::arg()["chroot"].c_str())<0 || chdir("/") < 0) {
164 cerr
<<"Unable to chroot to '"+::arg()["chroot"]+"': "<<strerror (errno
)<<endl
;
172 static bool rectifyZone(DNSSECKeeper
& dk
, const DNSName
& zone
, bool quiet
= false, bool rectifyTransaction
= true)
176 bool ret
= dk
.rectifyZone(zone
, error
, output
, rectifyTransaction
);
177 if (!quiet
|| !ret
) {
178 // When quiet, only print output if there was an error
179 if (!output
.empty()) {
182 if (!ret
&& !error
.empty()) {
189 static void dbBench(const std::string
& fname
)
191 ::arg().set("query-cache-ttl")="0";
192 ::arg().set("negquery-cache-ttl")="0";
193 UeberBackend
B("default");
195 vector
<string
> domains
;
197 ifstream
ifs(fname
.c_str());
199 cerr
<< "Could not open '" << fname
<< "' for reading zone names to query" << endl
;
202 while(getline(ifs
,line
)) {
204 domains
.push_back(line
);
208 domains
.push_back("powerdns.com");
214 unsigned int hits
=0, misses
=0;
215 for(; n
< 10000; ++n
) {
216 DNSName
domain(domains
[dns_random(domains
.size())]);
217 B
.lookup(QType(QType::NS
), domain
, -1);
221 B
.lookup(QType(QType::A
), DNSName(std::to_string(dns_random_uint32()))+domain
, -1);
227 cout
<<0.001*dt
.udiff()/n
<<" millisecond/lookup"<<endl
;
228 cout
<<"Retrieved "<<hits
<<" records, did "<<misses
<<" queries which should have no match"<<endl
;
229 cout
<<"Packet cache reports: "<<S
.read("query-cache-hit")<<" hits (should be 0) and "<<S
.read("query-cache-miss") <<" misses"<<endl
;
232 static bool rectifyAllZones(DNSSECKeeper
&dk
, bool quiet
= false)
234 UeberBackend
B("default");
235 vector
<DomainInfo
> domainInfo
;
238 B
.getAllDomains(&domainInfo
, false, false);
239 for(const DomainInfo
& di
: domainInfo
) {
241 cerr
<<"Rectifying "<<di
.zone
<<": ";
243 if (!rectifyZone(dk
, di
.zone
, quiet
)) {
248 cout
<<"Rectified "<<domainInfo
.size()<<" zones."<<endl
;
253 static int checkZone(DNSSECKeeper
&dk
, UeberBackend
&B
, const DNSName
& zone
, const vector
<DNSResourceRecord
>* suppliedrecords
=nullptr)
255 uint64_t numerrors
=0, numwarnings
=0;
259 if (!B
.getDomainInfo(zone
, di
, false)) {
260 cout
<< "[Error] Unable to get zone information for zone '" << zone
<< "'" << endl
;
263 } catch(const PDNSException
&e
) {
264 if (di
.kind
== DomainInfo::Secondary
) {
265 cout
<< "[Error] non-IP address for primaries: " << e
.reason
<< endl
;
272 if (!B
.getSOAUncached(zone
, sd
)) {
273 cout
<< "[Error] No SOA record present, or active, in zone '" << zone
<< "'" << endl
;
275 cout
<< "Checked 0 records of '" << zone
<< "', " << numerrors
<< " errors, 0 warnings." << endl
;
279 catch (const PDNSException
& e
) {
280 cout
<< "[Error] SOA lookup failed for zone '" << zone
<< "': " << e
.reason
<< endl
;
282 if (sd
.db
== nullptr) {
286 catch (const std::exception
& e
) {
287 cout
<< "[Error] SOA lookup failed for zone '" << zone
<< "': " << e
.what() << endl
;
289 if (sd
.db
== nullptr) {
294 NSEC3PARAMRecordContent ns3pr
;
296 bool haveNSEC3
= dk
.getNSEC3PARAM(zone
, &ns3pr
, &narrow
);
297 bool isOptOut
=(haveNSEC3
&& ns3pr
.d_flags
);
299 bool isSecure
=dk
.isSecuredZone(zone
);
300 bool presigned
=dk
.isPresigned(zone
);
301 vector
<string
> checkKeyErrors
;
302 bool validKeys
=dk
.checkKeys(zone
, checkKeyErrors
);
305 if(isSecure
&& zone
.wirelength() > 222) {
307 cout
<<"[Error] zone '" << zone
<< "' has NSEC3 semantics but is too long to have the hash prepended. Zone name is " << zone
.wirelength() << " bytes long, whereas the maximum is 222 bytes." << endl
;
310 vector
<DNSBackend::KeyData
> dbkeyset
;
311 B
.getDomainKeys(zone
, dbkeyset
);
313 for (DNSBackend::KeyData
& kd
: dbkeyset
) {
314 DNSKEYRecordContent dkrc
;
315 DNSCryptoKeyEngine::makeFromISCString(dkrc
, kd
.content
);
317 if(dkrc
.d_algorithm
== DNSSECKeeper::RSASHA1
) {
318 cout
<<"[Error] zone '"<<zone
<<"' has NSEC3 semantics, but the "<< (kd
.active
? "" : "in" ) <<"active key with id "<<kd
.id
<<" has 'Algorithm: 5'. This should be corrected to 'Algorithm: 7' in the database (or NSEC3 should be disabled)."<<endl
;
326 cout
<<"[Error] zone '" << zone
<< "' has at least one invalid DNS Private Key." << endl
;
327 for (const auto &msg
: checkKeyErrors
) {
328 cout
<<"\t"<<msg
<<endl
;
332 // Check for delegation in parent zone
333 DNSName
parent(zone
);
334 while(parent
.chopOff()) {
336 if(B
.getSOAUncached(parent
, sd_p
)) {
339 B
.lookup(QType(QType::ANY
), zone
, sd_p
.domain_id
);
341 ns
|= (zr
.dr
.d_type
== QType::NS
);
343 cout
<<"[Error] No delegation for zone '"<<zone
<<"' in parent '"<<parent
<<"'"<<endl
;
351 bool hasNsAtApex
= false;
352 set
<DNSName
> tlsas
, cnames
, noncnames
, glue
, checkglue
, addresses
, svcbAliases
, httpsAliases
, svcbRecords
, httpsRecords
, arecords
, aaaarecords
;
353 vector
<DNSResourceRecord
> checkCNAME
;
354 set
<pair
<DNSName
, QType
> > checkOcclusion
;
355 set
<string
> recordcontents
;
356 map
<string
, unsigned int> ttl
;
357 // Record name, prio, target name, ipv4hint=auto, ipv6hint=auto
358 set
<std::tuple
<DNSName
, uint16_t, DNSName
, bool, bool> > svcbTargets
, httpsTargets
;
360 ostringstream content
;
361 pair
<map
<string
, unsigned int>::iterator
,bool> ret
;
363 vector
<DNSResourceRecord
> records
;
364 if(suppliedrecords
== nullptr) {
365 DNSResourceRecord drr
;
366 sd
.db
->list(zone
, sd
.domain_id
, g_verbose
);
367 while(sd
.db
->get(drr
)) {
368 records
.push_back(drr
);
372 records
=*suppliedrecords
;
374 for(auto &rr
: records
) { // we modify this
375 if(rr
.qtype
.getCode() == QType::TLSA
)
376 tlsas
.insert(rr
.qname
);
377 if(rr
.qtype
.getCode() == QType::A
|| rr
.qtype
.getCode() == QType::AAAA
) {
378 addresses
.insert(rr
.qname
);
380 if(rr
.qtype
.getCode() == QType::A
) {
381 arecords
.insert(rr
.qname
);
383 if(rr
.qtype
.getCode() == QType::AAAA
) {
384 aaaarecords
.insert(rr
.qname
);
386 if(rr
.qtype
.getCode() == QType::SOA
) {
388 stringtok(parts
, rr
.content
);
390 if(parts
.size() < 7) {
391 cout
<< "[Info] SOA autocomplete is deprecated, missing field(s) in SOA content: " << rr
.qname
<< " IN " << rr
.qtype
.toString() << " '" << rr
.content
<< "'" << endl
;
394 if(parts
.size() >= 2) {
395 if(parts
[1].find('@') != string::npos
) {
396 cout
<<"[Warning] Found @-sign in SOA RNAME, should probably be a dot (.): "<<rr
.qname
<<" IN " <<rr
.qtype
.toString()<< " '" << rr
.content
<<"'"<<endl
;
403 for(int pleft
=parts
.size(); pleft
< 7; ++pleft
) {
409 if(rr
.qtype
.getCode() == QType::TXT
&& !rr
.content
.empty() && rr
.content
[0]!='"')
410 rr
.content
= "\""+rr
.content
+"\"";
413 shared_ptr
<DNSRecordContent
> drc(DNSRecordContent::make(rr
.qtype
.getCode(), QClass::IN
, rr
.content
));
414 string tmp
=drc
->serialize(rr
.qname
);
415 tmp
= drc
->getZoneRepresentation(true);
416 if (rr
.qtype
.getCode() != QType::AAAA
) {
417 if (!pdns_iequals(tmp
, rr
.content
)) {
418 if(rr
.qtype
.getCode() == QType::SOA
) {
419 tmp
= drc
->getZoneRepresentation(false);
421 if(!pdns_iequals(tmp
, rr
.content
)) {
422 cout
<<"[Warning] Parsed and original record content are not equal: "<<rr
.qname
<<" IN " <<rr
.qtype
.toString()<< " '" << rr
.content
<<"' (Content parsed as '"<<tmp
<<"')"<<endl
;
427 struct in6_addr tmpbuf
;
428 if (inet_pton(AF_INET6
, rr
.content
.c_str(), &tmpbuf
) != 1 || rr
.content
.find('.') != string::npos
) {
429 cout
<<"[Warning] Following record is not a valid IPv6 address: "<<rr
.qname
<<" IN " <<rr
.qtype
.toString()<< " '" << rr
.content
<<"'"<<endl
;
434 catch(std::exception
& e
)
436 cout
<<"[Error] Following record had a problem: \""<<rr
.qname
<<" IN "<<rr
.qtype
.toString()<<" "<<rr
.content
<<"\""<<endl
;
437 cout
<<"[Error] Error was: "<<e
.what()<<endl
;
442 if(!rr
.qname
.isPartOf(zone
)) {
443 cout
<<"[Error] Record '"<<rr
.qname
<<" IN "<<rr
.qtype
.toString()<<" "<<rr
.content
<<"' in zone '"<<zone
<<"' is out-of-zone."<<endl
;
448 if (rr
.qtype
.getCode() == QType::SVCB
|| rr
.qtype
.getCode() == QType::HTTPS
) {
449 shared_ptr
<DNSRecordContent
> drc(DNSRecordContent::make(rr
.qtype
.getCode(), QClass::IN
, rr
.content
));
450 // I, too, like to live dangerously
451 auto svcbrc
= std::dynamic_pointer_cast
<SVCBBaseRecordContent
>(drc
);
452 if (svcbrc
->getPriority() == 0 && svcbrc
->hasParams()) {
453 cout
<<"[Warning] Aliasform "<<rr
.qtype
.toString()<<" record "<<rr
.qname
<<" has service parameters."<<endl
;
457 if(svcbrc
->getPriority() != 0) {
459 if (svcbrc
->hasParam(SvcParam::no_default_alpn
) && !svcbrc
->hasParam(SvcParam::alpn
)) {
460 /* draft-ietf-dnsop-svcb-https-03 section 6.1
461 * When "no-default-alpn" is specified in an RR, "alpn" must
462 * also be specified in order for the RR to be "self-consistent
465 cout
<<"[Warning] "<<rr
.qname
<<"|"<<rr
.qtype
.toString()<<" is not self-consistent: 'no-default-alpn' parameter without 'alpn' parameter"<<endl
;
468 if (svcbrc
->hasParam(SvcParam::mandatory
)) {
469 auto keys
= svcbrc
->getParam(SvcParam::mandatory
).getMandatory();
470 for (auto const &k
: keys
) {
471 if (!svcbrc
->hasParam(k
)) {
472 cout
<<"[Warning] "<<rr
.qname
<<"|"<<rr
.qtype
.toString()<<" is not self-consistent: 'mandatory' parameter lists '"+ SvcParam::keyToString(k
) +"', but that parameter does not exist"<<endl
;
479 switch (rr
.qtype
.getCode()) {
481 if (svcbrc
->getPriority() == 0) {
482 if (svcbAliases
.find(rr
.qname
) != svcbAliases
.end()) {
483 cout
<< "[Warning] More than one Alias form SVCB record for " << rr
.qname
<< " exists." << endl
;
486 svcbAliases
.insert(rr
.qname
);
488 svcbTargets
.emplace(rr
.qname
, svcbrc
->getPriority(), svcbrc
->getTarget(), svcbrc
->autoHint(SvcParam::ipv4hint
), svcbrc
->autoHint(SvcParam::ipv6hint
));
489 svcbRecords
.insert(rr
.qname
);
492 if (svcbrc
->getPriority() == 0) {
493 if (httpsAliases
.find(rr
.qname
) != httpsAliases
.end()) {
494 cout
<< "[Warning] More than one Alias form HTTPS record for " << rr
.qname
<< " exists." << endl
;
497 httpsAliases
.insert(rr
.qname
);
499 httpsTargets
.emplace(rr
.qname
, svcbrc
->getPriority(), svcbrc
->getTarget(), svcbrc
->autoHint(SvcParam::ipv4hint
), svcbrc
->autoHint(SvcParam::ipv6hint
));
500 httpsRecords
.insert(rr
.qname
);
506 content
<<rr
.qname
<<" "<<rr
.qtype
.toString()<<" "<<rr
.content
;
507 string contentstr
= content
.str();
508 if (rr
.qtype
.getCode() != QType::TXT
) {
509 contentstr
=toLower(contentstr
);
511 if (recordcontents
.count(contentstr
)) {
512 cout
<<"[Error] Duplicate record found in rrset: '"<<rr
.qname
<<" IN "<<rr
.qtype
.toString()<<" "<<rr
.content
<<"'"<<endl
;
516 recordcontents
.insert(contentstr
);
519 content
<<rr
.qname
<<" "<<rr
.qtype
.toString();
520 if (rr
.qtype
.getCode() == QType::RRSIG
) {
521 RRSIGRecordContent
rrc(rr
.content
);
522 content
<<" ("<<DNSRecordContent::NumberToType(rrc
.d_type
)<<")";
524 ret
= ttl
.insert(pair
<string
, unsigned int>(toLower(content
.str()), rr
.ttl
));
525 if (ret
.second
== false && ret
.first
->second
!= rr
.ttl
) {
526 cout
<<"[Error] TTL mismatch in rrset: '"<<rr
.qname
<<" IN " <<rr
.qtype
.toString()<<" "<<rr
.content
<<"' ("<<ret
.first
->second
<<" != "<<rr
.ttl
<<")"<<endl
;
531 if (isSecure
&& isOptOut
&& (rr
.qname
.countLabels() && rr
.qname
.getRawLabels()[0] == "*")) {
532 cout
<<"[Warning] wildcard record '"<<rr
.qname
<<" IN " <<rr
.qtype
.toString()<<" "<<rr
.content
<<"' is insecure"<<endl
;
533 cout
<<"[Info] Wildcard records in opt-out zones are insecure. Disable the opt-out flag for this zone to avoid this warning. Command: pdnsutil set-nsec3 "<<zone
<<endl
;
539 if (rr
.qtype
.getCode() == QType::NS
) {
541 } else if (rr
.qtype
.getCode() == QType::DS
) {
542 cout
<<"[Warning] DS at apex in zone '"<<zone
<<"', should not be here."<<endl
;
547 if (rr
.qtype
.getCode() == QType::SOA
) {
548 cout
<<"[Error] SOA record not at apex '"<<rr
.qname
<<" IN "<<rr
.qtype
.toString()<<" "<<rr
.content
<<"' in zone '"<<zone
<<"'"<<endl
;
551 } else if (rr
.qtype
.getCode() == QType::DNSKEY
) {
552 cout
<<"[Warning] DNSKEY record not at apex '"<<rr
.qname
<<" IN "<<rr
.qtype
.toString()<<" "<<rr
.content
<<"' in zone '"<<zone
<<"', should not be here."<<endl
;
554 } else if (rr
.qtype
.getCode() == QType::NS
) {
555 if (DNSName(rr
.content
).isPartOf(rr
.qname
)) {
556 checkglue
.insert(DNSName(toLower(rr
.content
)));
558 checkOcclusion
.insert({rr
.qname
, rr
.qtype
});
559 } else if (rr
.qtype
.getCode() == QType::A
|| rr
.qtype
.getCode() == QType::AAAA
) {
560 glue
.insert(rr
.qname
);
564 // DNAMEs can occur both at the apex and below it
565 if (rr
.qtype
== QType::DNAME
) {
566 checkOcclusion
.insert({rr
.qname
, rr
.qtype
});
569 if((rr
.qtype
.getCode() == QType::A
|| rr
.qtype
.getCode() == QType::AAAA
) && !rr
.qname
.isWildcard() && !rr
.qname
.isHostname())
570 cout
<<"[Info] "<<rr
.qname
.toString()<<" record for '"<<rr
.qtype
.toString()<<"' is not a valid hostname."<<endl
;
572 // Check if the DNSNames that should be hostnames, are hostnames
574 checkHostnameCorrectness(rr
);
575 } catch (const std::exception
& e
) {
576 cout
<< "[Warning] " << rr
.qtype
.toString() << " record in zone '" << zone
<< ": " << e
.what() << endl
;
580 if (rr
.qtype
.getCode() == QType::CNAME
) {
581 if (!cnames
.count(rr
.qname
))
582 cnames
.insert(rr
.qname
);
584 cout
<<"[Error] Duplicate CNAME found at '"<<rr
.qname
<<"'"<<endl
;
589 if (rr
.qtype
.getCode() == QType::RRSIG
) {
591 cout
<<"[Error] RRSIG found at '"<<rr
.qname
<<"' in non-presigned zone. These do not belong in the database."<<endl
;
596 noncnames
.insert(rr
.qname
);
599 if (rr
.qtype
== QType::MX
|| rr
.qtype
== QType::NS
|| rr
.qtype
== QType::SRV
) {
600 checkCNAME
.push_back(rr
);
603 if(rr
.qtype
.getCode() == QType::NSEC
|| rr
.qtype
.getCode() == QType::NSEC3
)
605 cout
<<"[Error] NSEC or NSEC3 found at '"<<rr
.qname
<<"'. These do not belong in the database."<<endl
;
610 if(!presigned
&& rr
.qtype
.getCode() == QType::DNSKEY
)
612 if(::arg().mustDo("direct-dnskey"))
614 if(rr
.ttl
!= sd
.minimum
)
616 cout
<<"[Warning] DNSKEY TTL of "<<rr
.ttl
<<" at '"<<rr
.qname
<<"' differs from SOA minimum of "<<sd
.minimum
<<endl
;
622 cout
<<"[Warning] DNSKEY at '"<<rr
.qname
<<"' in non-presigned zone will mostly be ignored and can cause problems."<<endl
;
628 for(auto &i
: cnames
) {
629 if (noncnames
.find(i
) != noncnames
.end()) {
630 cout
<<"[Error] CNAME "<<i
<<" found, but other records with same label exist."<<endl
;
635 for(const auto &i
: tlsas
) {
636 DNSName name
= DNSName(i
);
637 name
.trimToLabels(name
.countLabels()-2);
638 if (cnames
.find(name
) == cnames
.end() && noncnames
.find(name
) == noncnames
.end()) {
639 // No specific record for the name in the TLSA record exists, this
640 // is already worth emitting a warning. Let's see if a wildcard exist.
642 DNSName
wcname(name
);
644 wcname
.prependRawLabel("*");
645 if (cnames
.find(wcname
) != cnames
.end() || noncnames
.find(wcname
) != noncnames
.end()) {
646 cout
<<"A wildcard record exist for '"<<wcname
<<"' and a TLSA record for '"<<i
<<"'.";
648 cout
<<"No record for '"<<name
<<"' exists, but a TLSA record for '"<<i
<<"' does.";
651 cout
<<" A query for '"<<name
<<"' will yield an empty response. This is most likely a mistake, please create records for '"<<name
<<"'."<<endl
;
655 for (const auto &svcb
: svcbTargets
) {
656 const auto& name
= std::get
<0>(svcb
);
657 const auto& target
= std::get
<2>(svcb
);
658 auto prio
= std::get
<1>(svcb
);
659 auto v4hintsAuto
= std::get
<3>(svcb
);
660 auto v6hintsAuto
= std::get
<4>(svcb
);
662 if (name
== target
) {
663 cout
<<"[Error] SVCB record "<<name
<<" has itself as target."<<endl
;
668 if (target
.isPartOf(zone
)) {
669 if (svcbAliases
.find(target
) != svcbAliases
.end()) {
670 cout
<< "[Warning] SVCB record for " << name
<< " has an aliasform target (" << target
<< ") that is in aliasform itself." << endl
;
673 if (addresses
.find(target
) == addresses
.end() && svcbRecords
.find(target
) == svcbRecords
.end()) {
674 cout
<<"[Error] SVCB record "<<name
<<" has a target "<<target
<<" that has neither address nor SVCB records."<<endl
;
680 auto trueTarget
= target
.isRoot() ? name
: target
;
682 if(v4hintsAuto
&& arecords
.find(trueTarget
) == arecords
.end()) {
683 cout
<< "[warning] SVCB record for "<< name
<< " has automatic IPv4 hints, but no A-record for the target at "<< trueTarget
<<" exists."<<endl
;
686 if(v6hintsAuto
&& aaaarecords
.find(trueTarget
) == aaaarecords
.end()) {
687 cout
<< "[warning] SVCB record for "<< name
<< " has automatic IPv6 hints, but no AAAA-record for the target at "<< trueTarget
<<" exists."<<endl
;
693 for (const auto &httpsRecord
: httpsTargets
) {
694 const auto& name
= std::get
<0>(httpsRecord
);
695 const auto& target
= std::get
<2>(httpsRecord
);
696 auto prio
= std::get
<1>(httpsRecord
);
697 auto v4hintsAuto
= std::get
<3>(httpsRecord
);
698 auto v6hintsAuto
= std::get
<4>(httpsRecord
);
700 if (name
== target
) {
701 cout
<<"[Error] HTTPS record "<<name
<<" has itself as target."<<endl
;
706 if (target
.isPartOf(zone
)) {
707 if (httpsAliases
.find(target
) != httpsAliases
.end()) {
708 cout
<< "[Warning] HTTPS record for " << name
<< " has an aliasform target (" << target
<< ") that is in aliasform itself." << endl
;
711 if (addresses
.find(target
) == addresses
.end() && httpsRecords
.find(target
) == httpsRecords
.end()) {
712 cout
<<"[Error] HTTPS record "<<name
<<" has a target "<<target
<<" that has neither address nor HTTPS records."<<endl
;
718 auto trueTarget
= target
.isRoot() ? name
: target
;
720 if(v4hintsAuto
&& arecords
.find(trueTarget
) == arecords
.end()) {
721 cout
<< "[warning] HTTPS record for "<< name
<< " has automatic IPv4 hints, but no A-record for the target at "<< trueTarget
<<" exists."<<endl
;
724 if(v6hintsAuto
&& aaaarecords
.find(trueTarget
) == aaaarecords
.end()) {
725 cout
<< "[warning] HTTPS record for "<< name
<< " has automatic IPv6 hints, but no AAAA-record for the target at "<< trueTarget
<<" exists."<<endl
;
732 cout
<<"[Error] No NS record at zone apex in zone '"<<zone
<<"'"<<endl
;
736 for(const auto &qname
: checkglue
) {
737 if (!glue
.count(qname
)) {
738 cout
<<"[Warning] Missing glue for '"<<qname
<<"' in zone '"<<zone
<<"'"<<endl
;
743 for( const auto &qname
: checkOcclusion
) {
744 for( const auto &rr
: records
) {
745 // a name does not occlude itself in the following situations:
746 // NS does not occlude DS+NS
747 // a DNAME does not occlude itself
748 if( qname
.first
== rr
.qname
&& ((( rr
.qtype
== QType::NS
|| rr
.qtype
== QType::DS
) && qname
.second
== QType::NS
) || ( rr
.qtype
== QType::DNAME
&& qname
.second
== QType::DNAME
) ) ) {
752 // for most types, X occludes X and (type-dependent) almost everything under X
753 if( rr
.qname
.isPartOf( qname
.first
) ) {
755 // but a DNAME does not occlude anything at its name, only the things under it
756 if( qname
.second
== QType::DNAME
&& rr
.qname
== qname
.first
) {
760 // the record under inspection is:
761 // occluded by a DNAME, or
762 // occluded by a delegation, and is not glue or ENTs leading towards that glue
763 if( qname
.second
== QType::DNAME
|| ( rr
.qtype
!= QType::ENT
&& rr
.qtype
.getCode() != QType::A
&& rr
.qtype
.getCode() != QType::AAAA
) ) {
764 cout
<< "[Warning] '" << rr
.qname
<< "|" << rr
.qtype
.toString() << "' in zone '" << zone
<< "' is occluded by a ";
765 if( qname
.second
== QType::NS
) {
766 cout
<< "delegation";
770 cout
<< " at '" << qname
.first
<< "'" << endl
;
777 for (auto const &rr
: checkCNAME
) {
779 shared_ptr
<DNSRecordContent
> drc(DNSRecordContent::make(rr
.qtype
.getCode(), QClass::IN
, rr
.content
));
782 target
= std::dynamic_pointer_cast
<MXRecordContent
>(drc
)->d_mxname
;
785 target
= std::dynamic_pointer_cast
<SRVRecordContent
>(drc
)->d_target
;
788 target
= std::dynamic_pointer_cast
<NSRecordContent
>(drc
)->getNS();
791 // programmer error, but let's not abort() :)
794 if (target
.isPartOf(zone
) && cnames
.count(target
) != 0) {
795 cout
<<"[Warning] '" << rr
.qname
<< "|" << rr
.qtype
.toString() << " has a target (" << target
<< ") that is a CNAME." << endl
;
800 bool ok
, ds_ns
, done
;
801 for( const auto &rr
: records
) {
802 ok
= ( rr
.auth
== 1 );
804 done
= (suppliedrecords
!= nullptr || !sd
.db
->doesDNSSEC());
805 for( const auto &qname
: checkOcclusion
) {
806 if( qname
.second
== QType::NS
) {
807 if( qname
.first
== rr
.qname
) {
814 if( rr
.qname
.isPartOf( qname
.first
) && ( qname
.first
!= rr
.qname
|| rr
.qtype
!= QType::DS
) ) {
817 if( rr
.qtype
== QType::ENT
&& qname
.first
.isPartOf( rr
.qname
) ) {
820 } else if( rr
.qname
.isPartOf( qname
.first
) && ( ( qname
.first
!= rr
.qname
|| rr
.qtype
!= QType::DS
) || rr
.qtype
== QType::NS
) ) {
826 if( ! ds_ns
&& rr
.qtype
.getCode() == QType::DS
&& rr
.qname
!= zone
) {
827 cout
<< "[Warning] DS record without a delegation '" << rr
.qname
<<"'." << endl
;
830 if( ! ok
&& suppliedrecords
== nullptr ) {
831 cout
<< "[Error] Following record is auth=" << rr
.auth
<< ", run pdnsutil rectify-zone?: " << rr
.qname
<< " IN " << rr
.qtype
.toString() << " " << rr
.content
<< endl
;
836 std::map
<std::string
, std::vector
<std::string
>> metadatas
;
837 if (B
.getAllDomainMetadata(zone
, metadatas
)) {
838 for (const auto& metaData
: metadatas
) {
840 set
<string
> messaged
;
842 for (const auto& value
: metaData
.second
) {
843 if (seen
.count(value
) == 0) {
846 else if (messaged
.count(value
) <= 0) {
847 cout
<< "[Error] Found duplicate metadata key value pair for zone " << zone
<< " with key '" << metaData
.first
<< "' and value '" << value
<< "'" << endl
;
849 messaged
.insert(value
);
855 cout
<<"Checked "<<records
.size()<<" records of '"<<zone
<<"', "<<numerrors
<<" errors, "<<numwarnings
<<" warnings."<<endl
;
861 static int checkAllZones(DNSSECKeeper
&dk
, bool exitOnError
)
863 UeberBackend
B("default");
864 vector
<DomainInfo
> domainInfo
;
865 multi_index_container
<
868 ordered_non_unique
< member
<DomainInfo
,DNSName
,&DomainInfo::zone
>, CanonDNSNameCompare
>,
869 ordered_non_unique
< member
<DomainInfo
,uint32_t,&DomainInfo::id
> >
872 auto& seenNames
= seenInfos
.get
<0>();
873 auto& seenIds
= seenInfos
.get
<1>();
875 B
.getAllDomains(&domainInfo
, true, true);
877 for (auto& di
: domainInfo
) {
878 if (checkZone(dk
, B
, di
.zone
) > 0) {
882 auto seenName
= seenNames
.find(di
.zone
);
883 if (seenName
!= seenNames
.end()) {
884 cout
<<"[Error] Another SOA for zone '"<<di
.zone
<<"' (serial "<<di
.serial
<<") has already been seen (serial "<<seenName
->serial
<<")."<<endl
;
888 auto seenId
= seenIds
.find(di
.id
);
889 if (seenId
!= seenIds
.end()) {
890 cout
<< "[Error] Zone ID " << di
.id
<< " of '" << di
.zone
<< "' in backend " << di
.backend
->getPrefix() << " has already been used by zone '" << seenId
->zone
<< "' in backend " << seenId
->backend
->getPrefix() << "." << endl
;
894 seenInfos
.insert(std::move(di
));
896 if (errors
&& exitOnError
) {
900 cout
<<"Checked "<<domainInfo
.size()<<" zones, "<<errors
<<" had errors."<<endl
;
906 static int increaseSerial(const DNSName
& zone
, DNSSECKeeper
&dk
)
908 UeberBackend
B("default");
910 if(!B
.getSOAUncached(zone
, sd
)) {
911 cerr
<<"No SOA for zone '"<<zone
<<"'"<<endl
;
915 if (dk
.isPresigned(zone
)) {
916 cerr
<<"Serial increase of presigned zone '"<<zone
<<"' is not allowed."<<endl
;
921 dk
.getSoaEdit(zone
, soaEditKind
);
923 DNSResourceRecord rr
;
924 makeIncreasedSOARecord(sd
, "SOA-EDIT-INCREASE", soaEditKind
, rr
);
926 sd
.db
->startTransaction(zone
, -1);
928 if (!sd
.db
->replaceRRSet(sd
.domain_id
, zone
, rr
.qtype
, vector
<DNSResourceRecord
>(1, rr
))) {
929 sd
.db
->abortTransaction();
930 cerr
<<"Backend did not replace SOA record. Backend might not support this operation."<<endl
;
934 if (sd
.db
->doesDNSSEC()) {
935 NSEC3PARAMRecordContent ns3pr
;
937 bool haveNSEC3
=dk
.getNSEC3PARAM(zone
, &ns3pr
, &narrow
);
942 ordername
=DNSName(toBase32Hex(hashQNameWithSalt(ns3pr
, zone
)));
944 ordername
=DNSName("");
946 cerr
<<"'"<<rr
.qname
<<"' -> '"<< ordername
<<"'"<<endl
;
947 sd
.db
->updateDNSSECOrderNameAndAuth(sd
.domain_id
, rr
.qname
, ordername
, true);
950 sd
.db
->commitTransaction();
952 cout
<<"SOA serial for zone "<<zone
<<" set to "<<sd
.serial
<<endl
;
956 static int deleteZone(const DNSName
&zone
) {
959 if (! B
.getDomainInfo(zone
, di
)) {
960 cerr
<< "Zone '" << zone
<< "' not found!" << endl
;
964 di
.backend
->startTransaction(zone
, -1);
966 if(di
.backend
->deleteDomain(zone
)) {
967 di
.backend
->commitTransaction();
971 di
.backend
->abortTransaction();
975 di
.backend
->abortTransaction();
977 cerr
<< "Failed to delete zone '" << zone
<< "'" << endl
;
982 static void listKey(DomainInfo
const &di
, DNSSECKeeper
& dk
, bool printHeader
= true) {
984 cout
<<"Zone Type Act Pub Size Algorithm ID Location Keytag"<<endl
;
985 cout
<<"------------------------------------------------------------------------------------------"<<endl
;
987 unsigned int spacelen
= 0;
988 for (auto const &key
: dk
.getKeys(di
.zone
)) {
990 if (di
.zone
.toStringNoDot().length() > 29)
991 cout
<<endl
<<string(30, ' ');
993 cout
<<string(30 - di
.zone
.toStringNoDot().length(), ' ');
995 cout
<<DNSSECKeeper::keyTypeToString(key
.second
.keyType
)<<" ";
997 if (key
.second
.active
) {
1003 if (key
.second
.published
) {
1009 spacelen
= (std::to_string(key
.first
.getKey()->getBits()).length() >= 8) ? 1 : 8 - std::to_string(key
.first
.getKey()->getBits()).length();
1010 if (key
.first
.getKey()->getBits() < 1) {
1011 cout
<<"invalid "<<endl
;
1014 cout
<<key
.first
.getKey()->getBits()<<string(spacelen
, ' ');
1017 string algname
= DNSSECKeeper::algorithm2name(key
.first
.getAlgorithm());
1018 spacelen
= (algname
.length() >= 16) ? 1 : 16 - algname
.length();
1019 cout
<<algname
<<string(spacelen
, ' ');
1021 spacelen
= (std::to_string(key
.second
.id
).length() > 5) ? 1 : 5 - std::to_string(key
.second
.id
).length();
1022 cout
<<key
.second
.id
<<string(spacelen
, ' ');
1025 auto stormap
= key
.first
.getKey()->convertToISCVector();
1026 string engine
, slot
, label
= "";
1027 for (auto const &elem
: stormap
) {
1028 //cout<<elem.first<<" "<<elem.second<<endl;
1029 if (elem
.first
== "Engine")
1030 engine
= elem
.second
;
1031 if (elem
.first
== "Slot")
1033 if (elem
.first
== "Label")
1034 label
= elem
.second
;
1036 if (engine
.empty() || slot
.empty()){
1037 cout
<<"cryptokeys ";
1039 spacelen
= (engine
.length()+slot
.length()+label
.length()+2 >= 12) ? 1 : 12 - engine
.length()-slot
.length()-label
.length()-2;
1040 cout
<<engine
<<","<<slot
<<","<<label
<<string(spacelen
, ' ');
1043 cout
<<"cryptokeys ";
1045 cout
<<key
.first
.getDNSKEY().getTag()<<endl
;
1049 static int listKeys(const string
&zname
, DNSSECKeeper
& dk
){
1050 UeberBackend
B("default");
1052 if (!zname
.empty()) {
1054 if(!B
.getDomainInfo(DNSName(zname
), di
)) {
1055 cerr
<< "Zone "<<zname
<<" not found."<<endl
;
1056 return EXIT_FAILURE
;
1060 vector
<DomainInfo
> domainInfo
;
1061 B
.getAllDomains(&domainInfo
, false, g_verbose
);
1062 bool printHeader
= true;
1063 for (const auto& di
: domainInfo
) {
1064 listKey(di
, dk
, printHeader
);
1065 printHeader
= false;
1068 return EXIT_SUCCESS
;
1071 static int listZone(const DNSName
&zone
) {
1075 if (! B
.getDomainInfo(zone
, di
)) {
1076 cerr
<< "Zone '" << zone
<< "' not found!" << endl
;
1077 return EXIT_FAILURE
;
1079 di
.backend
->list(zone
, di
.id
);
1080 DNSResourceRecord rr
;
1081 cout
<<"$ORIGIN ."<<endl
;
1082 cout
.sync_with_stdio(false);
1084 while(di
.backend
->get(rr
)) {
1085 if(rr
.qtype
.getCode()) {
1086 if ( (rr
.qtype
.getCode() == QType::NS
|| rr
.qtype
.getCode() == QType::SRV
|| rr
.qtype
.getCode() == QType::MX
|| rr
.qtype
.getCode() == QType::CNAME
) && !rr
.content
.empty() && rr
.content
[rr
.content
.size()-1] != '.')
1087 rr
.content
.append(1, '.');
1089 cout
<<rr
.qname
<<"\t"<<rr
.ttl
<<"\tIN\t"<<rr
.qtype
.toString()<<"\t"<<rr
.content
<<"\n";
1093 return EXIT_SUCCESS
;
1096 // lovingly copied from http://stackoverflow.com/questions/1798511/how-to-avoid-press-enter-with-any-getchar
1097 static int read1char(){
1099 static struct termios oldt
, newt
;
1101 /*tcgetattr gets the parameters of the current terminal
1102 STDIN_FILENO will tell tcgetattr that it should write the settings
1104 tcgetattr( STDIN_FILENO
, &oldt
);
1105 /*now the settings will be copied*/
1108 /*ICANON normally takes care that one line at a time will be processed
1109 that means it will return if it sees a "\n" or an EOF or an EOL*/
1110 newt
.c_lflag
&= ~(ICANON
);
1112 /*Those new settings will be set to STDIN
1113 TCSANOW tells tcsetattr to change attributes immediately. */
1114 tcsetattr( STDIN_FILENO
, TCSANOW
, &newt
);
1118 /*restore the old settings*/
1119 tcsetattr( STDIN_FILENO
, TCSANOW
, &oldt
);
1124 static int clearZone(const DNSName
&zone
) {
1128 if (! B
.getDomainInfo(zone
, di
)) {
1129 cerr
<< "Zone '" << zone
<< "' not found!" << endl
;
1130 return EXIT_FAILURE
;
1132 if(!di
.backend
->startTransaction(zone
, di
.id
)) {
1133 cerr
<<"Unable to start transaction for load of zone '"<<zone
<<"'"<<endl
;
1134 return EXIT_FAILURE
;
1136 di
.backend
->commitTransaction();
1137 return EXIT_SUCCESS
;
1143 PDNSColors(bool nocolors
)
1144 : d_colors(!nocolors
&& isatty(STDOUT_FILENO
) && getenv("NO_COLORS") == nullptr)
1147 [[nodiscard
]] string
red() const
1149 return d_colors
? "\x1b[31m" : "";
1151 [[nodiscard
]] string
green() const
1153 return d_colors
? "\x1b[32m" : "";
1155 [[nodiscard
]] string
bold() const
1157 return d_colors
? "\x1b[1m" : "";
1159 [[nodiscard
]] string
rst() const
1161 return d_colors
? "\x1b[0m" : "";
1167 static int editZone(const DNSName
&zone
, const PDNSColors
& col
) {
1170 DNSSECKeeper
dk(&B
);
1172 if (! B
.getDomainInfo(zone
, di
)) {
1173 cerr
<< "Zone '" << zone
<< "' not found!" << endl
;
1174 return EXIT_FAILURE
;
1177 /* ensure that the temporary file will only
1178 be accessible by the current user, not even
1179 by other users in the same group, and certainly
1182 umask(S_IRGRP
|S_IWGRP
|S_IROTH
|S_IWOTH
);
1183 vector
<DNSRecord
> pre
, post
;
1184 char tmpnam
[]="/tmp/pdnsutil-XXXXXX";
1185 int tmpfd
=mkstemp(tmpnam
);
1187 unixDie("Making temporary filename in "+string(tmpnam
));
1189 ~deleteme() { unlink(d_name
.c_str()); }
1190 deleteme(string name
) : d_name(std::move(name
)) {}
1194 vector
<DNSResourceRecord
> checkrr
;
1196 string editor
="editor";
1197 if(auto e
=getenv("EDITOR")) // <3
1201 di
.backend
->list(zone
, di
.id
);
1202 pre
.clear(); post
.clear();
1204 if(tmpfd
< 0 && (tmpfd
=open(tmpnam
, O_CREAT
| O_WRONLY
| O_TRUNC
, 0600)) < 0)
1205 unixDie("Error reopening temporary file "+string(tmpnam
));
1206 string
header("; Warning - every name in this file is ABSOLUTE!\n$ORIGIN .\n");
1207 if(write(tmpfd
, header
.c_str(), header
.length()) < 0)
1208 unixDie("Writing zone to temporary file");
1209 DNSResourceRecord rr
;
1210 while(di
.backend
->get(rr
)) {
1211 if(!rr
.qtype
.getCode())
1216 sort(pre
.begin(), pre
.end(), DNSRecord::prettyCompare
);
1217 for(const auto& dr
: pre
) {
1219 os
<<dr
.d_name
<<"\t"<<dr
.d_ttl
<<"\tIN\t"<<DNSRecordContent::NumberToType(dr
.d_type
)<<"\t"<<dr
.getContent()->getZoneRepresentation(true)<<endl
;
1220 if(write(tmpfd
, os
.str().c_str(), os
.str().length()) < 0)
1221 unixDie("Writing zone to temporary file");
1230 cmdline
+="+"+std::to_string(gotoline
)+" ";
1232 int err
=system(cmdline
.c_str());
1234 unixDie("Editing file with: '"+cmdline
+"', perhaps set EDITOR variable");
1237 ZoneParserTNG
zpt(tmpnam
, g_rootdnsname
);
1238 zpt
.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
1239 zpt
.setMaxIncludes(::arg().asNum("max-include-depth"));
1240 DNSResourceRecord zrr
;
1241 map
<pair
<DNSName
,uint16_t>, vector
<DNSRecord
> > grouped
;
1243 while(zpt
.get(zrr
)) {
1246 grouped
[{dr
.d_name
,dr
.d_type
}].push_back(dr
);
1249 catch(std::exception
& e
) {
1250 cerr
<<"Problem: "<<e
.what()<<" "<<zpt
.getLineOfFile()<<endl
;
1251 auto fnum
= zpt
.getLineNumAndFile();
1252 gotoline
= fnum
.second
;
1255 catch(PDNSException
& e
) {
1256 cerr
<<"Problem: "<<e
.reason
<<" "<<zpt
.getLineOfFile()<<endl
;
1257 auto fnum
= zpt
.getLineNumAndFile();
1258 gotoline
= fnum
.second
;
1262 sort(post
.begin(), post
.end(), DNSRecord::prettyCompare
);
1265 for(const DNSRecord
& rr
: post
) {
1266 DNSResourceRecord drr
= DNSResourceRecord::fromWire(rr
);
1267 drr
.domain_id
= di
.id
;
1268 checkrr
.push_back(drr
);
1270 if(checkZone(dk
, B
, zone
, &checkrr
)) {
1272 cerr
<< col
.red() << col
.bold() << "There was a problem with your zone" << col
.rst() << "\nOptions are: (e)dit your changes, (r)etry with original zone, (a)pply change anyhow, (q)uit: " << std::flush
;
1282 return EXIT_FAILURE
;
1289 vector
<DNSRecord
> diff
;
1291 map
<pair
<DNSName
,uint16_t>, string
> changed
;
1292 set_difference(pre
.cbegin(), pre
.cend(), post
.cbegin(), post
.cend(), back_inserter(diff
), DNSRecord::prettyCompare
);
1293 for(const auto& d
: diff
) {
1295 str
<< col
.red() << "-" << d
.d_name
<< " " << d
.d_ttl
<< " IN " << DNSRecordContent::NumberToType(d
.d_type
) << " " <<d
.getContent()->getZoneRepresentation(true) << col
.rst() <<endl
;
1296 changed
[{d
.d_name
,d
.d_type
}] += str
.str();
1300 set_difference(post
.cbegin(), post
.cend(), pre
.cbegin(), pre
.cend(), back_inserter(diff
), DNSRecord::prettyCompare
);
1301 for(const auto& d
: diff
) {
1304 str
<<col
.green() << "+" << d
.d_name
<< " " << d
.d_ttl
<< " IN " <<DNSRecordContent::NumberToType(d
.d_type
) << " " << d
.getContent()->getZoneRepresentation(true) << col
.rst() <<endl
;
1305 changed
[{d
.d_name
,d
.d_type
}]+=str
.str();
1307 cout
<<"Detected the following changes:"<<endl
;
1308 for(const auto& c
: changed
) {
1311 if (!changed
.empty()) {
1312 if (changed
.find({zone
, QType::SOA
}) == changed
.end()) {
1314 cout
<<endl
<<"You have not updated the SOA record! Would you like to increase-serial?"<<endl
;
1315 cout
<<"(y)es - increase serial, (n)o - leave SOA record as is, (e)dit your changes, (q)uit: "<<std::flush
;
1316 int c
= read1char();
1320 DNSRecord oldSoaDR
= grouped
[{zone
, QType::SOA
}].at(0); // there should be only one SOA record, so we can use .at(0);
1322 str
<< col
.red() << "-" << oldSoaDR
.d_name
<< " " << oldSoaDR
.d_ttl
<< " IN " << DNSRecordContent::NumberToType(oldSoaDR
.d_type
) << " " <<oldSoaDR
.getContent()->getZoneRepresentation(true) << col
.rst() <<endl
;
1325 B
.getSOAUncached(zone
, sd
);
1326 // TODO: do we need to check for presigned? here or maybe even all the way before edit-zone starts?
1329 dk
.getSoaEdit(zone
, soaEditKind
);
1331 DNSResourceRecord rr
;
1332 makeIncreasedSOARecord(sd
, "SOA-EDIT-INCREASE", soaEditKind
, rr
);
1334 str
<< col
.green() << "+" << dr
.d_name
<< " " << dr
.d_ttl
<< " IN " <<DNSRecordContent::NumberToType(dr
.d_type
) << " " <<dr
.getContent()->getZoneRepresentation(true) << col
.rst() <<endl
;
1336 changed
[{dr
.d_name
, dr
.d_type
}]+=str
.str();
1337 grouped
[{dr
.d_name
, dr
.d_type
}].at(0) = dr
;
1341 return EXIT_FAILURE
;
1352 if(changed
.empty()) {
1353 cout
<<endl
<<"No changes to apply."<<endl
;
1354 return(EXIT_SUCCESS
);
1356 cout
<<endl
<<"(a)pply these changes, (e)dit again, (r)etry with original zone, (q)uit: "<<std::flush
;
1361 return(EXIT_SUCCESS
);
1366 else if(changed
.empty() || c
!='a')
1369 di
.backend
->startTransaction(zone
, -1);
1370 for(const auto& change
: changed
) {
1371 vector
<DNSResourceRecord
> vrr
;
1372 for(const DNSRecord
& rr
: grouped
[change
.first
]) {
1373 DNSResourceRecord crr
= DNSResourceRecord::fromWire(rr
);
1374 crr
.domain_id
= di
.id
;
1377 di
.backend
->replaceRRSet(di
.id
, change
.first
.first
, QType(change
.first
.second
), vrr
);
1379 rectifyZone(dk
, zone
, false, false);
1380 di
.backend
->commitTransaction();
1381 return EXIT_SUCCESS
;
1384 #ifdef HAVE_IPCIPHER
1385 static int xcryptIP(const std::string
& cmd
, const std::string
& ip
, const std::string
& rkey
)
1388 ComboAddress
ca(ip
), ret
;
1390 if(cmd
=="ipencrypt")
1391 ret
= encryptCA(ca
, rkey
);
1393 ret
= decryptCA(ca
, rkey
);
1395 cout
<<ret
.toString()<<endl
;
1396 return EXIT_SUCCESS
;
1398 #endif /* HAVE_IPCIPHER */
1400 static int zonemdVerifyFile(const DNSName
& zone
, const string
& fname
) {
1401 ZoneParserTNG
zpt(fname
, zone
, "", true);
1402 zpt
.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
1404 bool validationDone
, validationOK
;
1407 auto zoneMD
= pdns::ZoneMD(zone
);
1408 zoneMD
.readRecords(zpt
);
1409 zoneMD
.verify(validationDone
, validationOK
);
1411 catch (const PDNSException
& ex
) {
1412 cerr
<< "zonemd-verify-file: " << ex
.reason
<< endl
;
1413 return EXIT_FAILURE
;
1415 catch (const std::exception
& ex
) {
1416 cerr
<< "zonemd-verify-file: " << ex
.what() << endl
;
1417 return EXIT_FAILURE
;
1420 if (validationDone
) {
1422 cout
<< "zonemd-verify-file: Verification of ZONEMD record succeeded" << endl
;
1423 return EXIT_SUCCESS
;
1425 cerr
<< "zonemd-verify-file: Verification of ZONEMD record(s) failed" << endl
;
1429 cerr
<< "zonemd-verify-file: No suitable ZONEMD record found to verify against" << endl
;
1431 return EXIT_FAILURE
;
1434 static int loadZone(const DNSName
& zone
, const string
& fname
) {
1438 if (B
.getDomainInfo(zone
, di
)) {
1439 cerr
<< "Zone '" << zone
<< "' exists already, replacing contents" << endl
;
1442 cerr
<<"Creating '"<<zone
<<"'"<<endl
;
1443 B
.createDomain(zone
, DomainInfo::Native
, vector
<ComboAddress
>(), "");
1445 if(!B
.getDomainInfo(zone
, di
)) {
1446 cerr
<< "Zone '" << zone
<< "' was not created - perhaps backend (" << ::arg()["launch"] << ") does not support storing new zones." << endl
;
1447 return EXIT_FAILURE
;
1450 DNSBackend
* db
= di
.backend
;
1451 ZoneParserTNG
zpt(fname
, zone
);
1452 zpt
.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
1454 DNSResourceRecord rr
;
1455 if(!db
->startTransaction(zone
, di
.id
)) {
1456 cerr
<<"Unable to start transaction for load of zone '"<<zone
<<"'"<<endl
;
1457 return EXIT_FAILURE
;
1460 bool haveSOA
= false;
1461 while(zpt
.get(rr
)) {
1462 if(!rr
.qname
.isPartOf(zone
) && rr
.qname
!=zone
) {
1463 cerr
<<"File contains record named '"<<rr
.qname
<<"' which is not part of zone '"<<zone
<<"'"<<endl
;
1464 return EXIT_FAILURE
;
1466 if (rr
.qtype
== QType::SOA
) {
1473 DNSRecordContent::make(rr
.qtype
, QClass::IN
, rr
.content
);
1475 catch (const PDNSException
&pe
) {
1476 cerr
<<"Bad record content in record for "<<rr
.qname
<<"|"<<rr
.qtype
.toString()<<": "<<pe
.reason
<<endl
;
1477 return EXIT_FAILURE
;
1479 catch (const std::exception
&e
) {
1480 cerr
<<"Bad record content in record for "<<rr
.qname
<<"|"<<rr
.qtype
.toString()<<": "<<e
.what()<<endl
;
1481 return EXIT_FAILURE
;
1483 db
->feedRecord(rr
, DNSName());
1485 db
->commitTransaction();
1486 return EXIT_SUCCESS
;
1489 static int createZone(const DNSName
&zone
, const DNSName
& nsname
) {
1492 if (B
.getDomainInfo(zone
, di
)) {
1493 cerr
<< "Zone '" << zone
<< "' exists already" << endl
;
1494 return EXIT_FAILURE
;
1497 DNSResourceRecord rr
;
1500 rr
.ttl
= ::arg().asNum("default-ttl");
1503 string soa
= ::arg()["default-soa-content"];
1504 boost::replace_all(soa
, "@", zone
.toStringNoDot());
1507 fillSOAData(soa
, sd
);
1509 catch(const std::exception
& e
) {
1510 cerr
<<"Error while parsing default-soa-content ("<<soa
<<"): "<<e
.what()<<endl
;
1511 cerr
<<"Zone not created!"<<endl
;
1512 return EXIT_FAILURE
;
1514 catch(const PDNSException
& pe
) {
1515 cerr
<<"Error while parsing default-soa-content ("<<soa
<<"): "<<pe
.reason
<<endl
;
1516 cerr
<<"Zone not created!"<<endl
;
1517 return EXIT_FAILURE
;
1520 rr
.content
= makeSOAContent(sd
)->getZoneRepresentation(true);
1522 cerr
<<"Creating empty zone '"<<zone
<<"'"<<endl
;
1523 B
.createDomain(zone
, DomainInfo::Native
, vector
<ComboAddress
>(), "");
1524 if(!B
.getDomainInfo(zone
, di
)) {
1525 cerr
<< "Zone '" << zone
<< "' was not created!" << endl
;
1526 return EXIT_FAILURE
;
1529 rr
.domain_id
= di
.id
;
1530 di
.backend
->startTransaction(zone
, di
.id
);
1531 di
.backend
->feedRecord(rr
, DNSName());
1532 if(!nsname
.empty()) {
1533 cout
<<"Also adding one NS record"<<endl
;
1535 rr
.content
=nsname
.toStringNoDot();
1536 di
.backend
->feedRecord(rr
, DNSName());
1539 di
.backend
->commitTransaction();
1541 return EXIT_SUCCESS
;
1544 static int createSecondaryZone(const vector
<string
>& cmds
)
1548 DNSName
zone(cmds
.at(1));
1549 if (B
.getDomainInfo(zone
, di
)) {
1550 cerr
<< "Zone '" << zone
<< "' exists already" << endl
;
1551 return EXIT_FAILURE
;
1553 vector
<ComboAddress
> primaries
;
1554 for (unsigned i
=2; i
< cmds
.size(); i
++) {
1555 primaries
.emplace_back(cmds
.at(i
), 53);
1557 cerr
<< "Creating secondary zone '" << zone
<< "', with primaries '" << comboAddressVecToString(primaries
) << "'" << endl
;
1558 B
.createDomain(zone
, DomainInfo::Secondary
, primaries
, "");
1559 if(!B
.getDomainInfo(zone
, di
)) {
1560 cerr
<< "Zone '" << zone
<< "' was not created!" << endl
;
1561 return EXIT_FAILURE
;
1563 return EXIT_SUCCESS
;
1566 static int changeSecondaryZonePrimary(const vector
<string
>& cmds
)
1570 DNSName
zone(cmds
.at(1));
1571 if (!B
.getDomainInfo(zone
, di
)) {
1572 cerr
<< "Zone '" << zone
<< "' doesn't exist" << endl
;
1573 return EXIT_FAILURE
;
1575 vector
<ComboAddress
> primaries
;
1576 for (unsigned i
=2; i
< cmds
.size(); i
++) {
1577 primaries
.emplace_back(cmds
.at(i
), 53);
1579 cerr
<< "Updating secondary zone '" << zone
<< "', primaries to '" << comboAddressVecToString(primaries
) << "'" << endl
;
1581 di
.backend
->setPrimaries(zone
, primaries
);
1582 return EXIT_SUCCESS
;
1584 catch (PDNSException
& e
) {
1585 cerr
<< "Setting primary for zone '" << zone
<< "' failed: " << e
.reason
<< endl
;
1586 return EXIT_FAILURE
;
1590 // add-record ZONE name type [ttl] "content" ["content"]
1591 static int addOrReplaceRecord(bool addOrReplace
, const vector
<string
>& cmds
) {
1592 DNSResourceRecord rr
;
1593 vector
<DNSResourceRecord
> newrrs
;
1594 DNSName
zone(cmds
.at(1));
1596 if (cmds
.at(2) == "@")
1599 name
= DNSName(cmds
.at(2)) + zone
;
1601 rr
.qtype
= DNSRecordContent::TypeToNumber(cmds
.at(3));
1602 rr
.ttl
= ::arg().asNum("default-ttl");
1606 if(!B
.getDomainInfo(zone
, di
)) {
1607 cerr
<< "Zone '" << zone
<< "' does not exist" << endl
;
1608 return EXIT_FAILURE
;
1611 rr
.domain_id
= di
.id
;
1613 DNSResourceRecord oldrr
;
1615 di
.backend
->startTransaction(zone
, -1);
1617 if(addOrReplace
) { // the 'add' case
1618 di
.backend
->lookup(rr
.qtype
, rr
.qname
, di
.id
);
1620 while(di
.backend
->get(oldrr
))
1621 newrrs
.push_back(oldrr
);
1624 unsigned int contentStart
= 4;
1625 if(cmds
.size() > 5) {
1626 rr
.ttl
= atoi(cmds
.at(4).c_str());
1627 if (std::to_string(rr
.ttl
) == cmds
.at(4)) {
1631 rr
.ttl
= ::arg().asNum("default-ttl");
1635 di
.backend
->lookup(QType(QType::ANY
), rr
.qname
, di
.id
);
1637 if(rr
.qtype
.getCode() == QType::CNAME
) { // this will save us SO many questions
1639 while(di
.backend
->get(oldrr
)) {
1640 if(addOrReplace
|| oldrr
.qtype
.getCode() != QType::CNAME
) // the replace case is ok if we replace one CNAME by the other
1644 cerr
<<"Attempting to add CNAME to "<<rr
.qname
<<" which already had existing records"<<endl
;
1645 return EXIT_FAILURE
;
1649 while(di
.backend
->get(oldrr
)) {
1650 if(oldrr
.qtype
.getCode() == QType::CNAME
)
1654 cerr
<<"Attempting to add record to "<<rr
.qname
<<" which already had a CNAME record"<<endl
;
1655 return EXIT_FAILURE
;
1660 cout
<<"Current records for "<<rr
.qname
<<" IN "<<rr
.qtype
.toString()<<" will be replaced"<<endl
;
1662 for(auto i
= contentStart
; i
< cmds
.size() ; ++i
) {
1663 rr
.content
= DNSRecordContent::make(rr
.qtype
.getCode(), QClass::IN
, cmds
.at(i
))->getZoneRepresentation(true);
1665 newrrs
.push_back(rr
);
1669 if(!di
.backend
->replaceRRSet(di
.id
, name
, rr
.qtype
, newrrs
)) {
1670 cerr
<<"backend did not accept the new RRset, aborting"<<endl
;
1671 return EXIT_FAILURE
;
1673 // need to be explicit to bypass the ueberbackend cache!
1674 di
.backend
->lookup(rr
.qtype
, name
, di
.id
);
1675 cout
<<"New rrset:"<<endl
;
1676 while(di
.backend
->get(rr
)) {
1677 cout
<<rr
.qname
.toString()<<" "<<rr
.ttl
<<" IN "<<rr
.qtype
.toString()<<" "<<rr
.content
<<endl
;
1679 di
.backend
->commitTransaction();
1680 return EXIT_SUCCESS
;
1683 // addAutoPrimary add a new autoprimary
1684 static int addAutoPrimary(const std::string
& IP
, const std::string
& nameserver
, const std::string
& account
)
1686 UeberBackend
B("default");
1687 const AutoPrimary
primary(IP
, nameserver
, account
);
1688 if (B
.autoPrimaryAdd(primary
)) {
1689 return EXIT_SUCCESS
;
1691 cerr
<<"could not find a backend with autosecondary support"<<endl
;
1692 return EXIT_FAILURE
;
1695 static int removeAutoPrimary(const std::string
&IP
, const std::string
&nameserver
)
1697 UeberBackend
B("default");
1698 const AutoPrimary
primary(IP
, nameserver
, "");
1699 if ( B
.autoPrimaryRemove(primary
) ){
1700 return EXIT_SUCCESS
;
1702 cerr
<<"could not find a backend with autosecondary support"<<endl
;
1703 return EXIT_FAILURE
;
1706 static int listAutoPrimaries()
1708 UeberBackend
B("default");
1709 vector
<AutoPrimary
> primaries
;
1710 if ( !B
.autoPrimariesList(primaries
) ){
1711 cerr
<<"could not find a backend with autosecondary support"<<endl
;
1712 return EXIT_FAILURE
;
1715 for(const auto& primary
: primaries
) {
1716 cout
<<"IP="<<primary
.ip
<<", NS="<<primary
.nameserver
<<", account="<<primary
.account
<<endl
;
1719 return EXIT_SUCCESS
;
1722 // delete-rrset zone name type
1723 static int deleteRRSet(const std::string
& zone_
, const std::string
& name_
, const std::string
& type_
)
1727 DNSName
zone(zone_
);
1728 if(!B
.getDomainInfo(zone
, di
)) {
1729 cerr
<< "Zone '" << zone
<< "' does not exist" << endl
;
1730 return EXIT_FAILURE
;
1737 name
=DNSName(name_
)+zone
;
1739 QType
qt(QType::chartocode(type_
.c_str()));
1740 di
.backend
->startTransaction(zone
, -1);
1741 di
.backend
->replaceRRSet(di
.id
, name
, qt
, vector
<DNSResourceRecord
>());
1742 di
.backend
->commitTransaction();
1743 return EXIT_SUCCESS
;
1746 static int listAllZones(const string
&type
="") {
1748 int kindFilter
= -1;
1749 if (!type
.empty()) {
1750 if (toUpper(type
) == "PRIMARY" || toUpper(type
) == "MASTER")
1752 else if (toUpper(type
) == "SECONDARY" || toUpper(type
) == "SLAVE")
1754 else if (toUpper(type
) == "NATIVE")
1756 else if (toUpper(type
) == "PRODUCER")
1758 else if (toUpper(type
) == "CONSUMER")
1761 cerr
<< "Syntax: pdnsutil list-all-zones [primary|secondary|native|producer|consumer]" << endl
;
1766 UeberBackend
B("default");
1768 vector
<DomainInfo
> domains
;
1769 B
.getAllDomains(&domains
, false, g_verbose
);
1772 for (const auto& di
: domains
) {
1773 if (di
.kind
== kindFilter
|| kindFilter
== -1) {
1774 cout
<<di
.zone
<<endl
;
1780 if (kindFilter
!= -1)
1781 cout
<<type
<<" zonecount: "<<count
<<endl
;
1783 cout
<<"All zonecount: "<<count
<<endl
;
1789 static int listMemberZones(const string
& catalog
)
1792 UeberBackend
B("default");
1794 DNSName
catz(catalog
);
1796 if (!B
.getDomainInfo(catz
, di
)) {
1797 cerr
<< "Zone '" << catz
<< "' not found" << endl
;
1798 return EXIT_FAILURE
;
1800 if (!di
.isCatalogType()) {
1801 cerr
<< "Zone '" << catz
<< "' is not a catalog zone" << endl
;
1802 return EXIT_FAILURE
;
1805 CatalogInfo::CatalogType type
;
1806 if (di
.kind
== DomainInfo::Producer
) {
1807 type
= CatalogInfo::Producer
;
1810 type
= CatalogInfo::Consumer
;
1813 vector
<CatalogInfo
> members
;
1814 if (!di
.backend
->getCatalogMembers(catz
, members
, type
)) {
1815 cerr
<< "Backend does not support catalog zones" << endl
;
1816 return EXIT_FAILURE
;
1819 for (const auto& ci
: members
) {
1820 cout
<< ci
.d_zone
<< endl
;
1824 cout
<< "All zonecount: " << members
.size() << endl
;
1827 return EXIT_SUCCESS
;
1830 static bool testAlgorithm(int algo
)
1832 return DNSCryptoKeyEngine::testOne(algo
);
1835 static bool testAlgorithms()
1837 return DNSCryptoKeyEngine::testAll();
1840 static void testSpeed(const DNSName
& zone
, const string
& /* remote */, int cores
)
1842 DNSResourceRecord rr
;
1843 rr
.qname
=DNSName("blah")+zone
;
1847 rr
.qclass
= QClass::IN
;
1849 UeberBackend
db("key-only");
1851 if ( db
.backends
.empty() )
1853 throw runtime_error("No backends available for DNSSEC key storage");
1856 ChunkedSigningPipe
csp(DNSName(zone
), true, cores
, 100);
1858 vector
<DNSZoneRecord
> signatures
;
1860 unsigned char* octets
= (unsigned char*)&rnd
;
1864 for(unsigned int n
=0; n
< 100000; ++n
) {
1865 rnd
= dns_random_uint32();
1866 snprintf(tmp
, sizeof(tmp
), "%d.%d.%d.%d",
1867 octets
[0], octets
[1], octets
[2], octets
[3]);
1870 snprintf(tmp
, sizeof(tmp
), "r-%u", rnd
);
1871 rr
.qname
=DNSName(tmp
)+zone
;
1873 dzr
.dr
=DNSRecord(rr
);
1875 while(signatures
= csp
.getChunk(), !signatures
.empty())
1878 cerr
<<"Flushing the pipe, "<<csp
.d_signed
<<" signed, "<<csp
.d_queued
<<" queued, "<<csp
.d_outstanding
<<" outstanding"<< endl
;
1879 cerr
<<"Net speed: "<<csp
.d_signed
/ (dt
.udiffNoReset()/1000000.0) << " sigs/s"<<endl
;
1880 while(signatures
= csp
.getChunk(true), !signatures
.empty())
1882 cerr
<<"Done, "<<csp
.d_signed
<<" signed, "<<csp
.d_queued
<<" queued, "<<csp
.d_outstanding
<<" outstanding"<< endl
;
1883 cerr
<<"Net speed: "<<csp
.d_signed
/ (dt
.udiff()/1000000.0) << " sigs/s"<<endl
;
1886 static void verifyCrypto(const string
& zone
)
1888 ZoneParserTNG
zpt(zone
);
1889 zpt
.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
1890 DNSResourceRecord rr
;
1891 DNSKEYRecordContent drc
;
1892 RRSIGRecordContent rrc
;
1893 DSRecordContent dsrc
;
1894 sortedRecords_t toSign
;
1895 DNSName qname
, apex
;
1896 dsrc
.d_digesttype
=0;
1897 while(zpt
.get(rr
)) {
1898 if(rr
.qtype
.getCode() == QType::DNSKEY
) {
1899 cerr
<<"got DNSKEY!"<<endl
;
1901 drc
= *std::dynamic_pointer_cast
<DNSKEYRecordContent
>(DNSRecordContent::make(QType::DNSKEY
, QClass::IN
, rr
.content
));
1903 else if(rr
.qtype
.getCode() == QType::RRSIG
) {
1904 cerr
<<"got RRSIG"<<endl
;
1905 rrc
= *std::dynamic_pointer_cast
<RRSIGRecordContent
>(DNSRecordContent::make(QType::RRSIG
, QClass::IN
, rr
.content
));
1907 else if(rr
.qtype
.getCode() == QType::DS
) {
1908 cerr
<<"got DS"<<endl
;
1909 dsrc
= *std::dynamic_pointer_cast
<DSRecordContent
>(DNSRecordContent::make(QType::DS
, QClass::IN
, rr
.content
));
1913 toSign
.insert(DNSRecordContent::make(rr
.qtype
.getCode(), QClass::IN
, rr
.content
));
1917 string msg
= getMessageForRRSET(qname
, rrc
, toSign
);
1918 cerr
<<"Verify: "<<DNSCryptoKeyEngine::makeFromPublicKeyString(drc
.d_algorithm
, drc
.d_key
)->verify(msg
, rrc
.d_signature
)<<endl
;
1919 if(dsrc
.d_digesttype
) {
1920 cerr
<<"Calculated DS: "<<apex
.toString()<<" IN DS "<<makeDSFromDNSKey(apex
, drc
, dsrc
.d_digesttype
).getZoneRepresentation()<<endl
;
1921 cerr
<<"Original DS: "<<apex
.toString()<<" IN DS "<<dsrc
.getZoneRepresentation()<<endl
;
1925 static bool disableDNSSECOnZone(DNSSECKeeper
& dk
, const DNSName
& zone
)
1927 UeberBackend
B("default");
1930 if (!B
.getDomainInfo(zone
, di
)){
1931 cerr
<< "No such zone in the database" << endl
;
1936 bool ret
= dk
.unSecureZone(zone
, error
);
1938 cerr
<< error
<< endl
;
1943 static int setZoneOptionsJson(const DNSName
& zone
, const string
& options
)
1945 UeberBackend
B("default");
1948 if (!B
.getDomainInfo(zone
, di
)) {
1949 cerr
<< "No such zone " << zone
<< " in the database" << endl
;
1950 return EXIT_FAILURE
;
1952 if (!di
.backend
->setOptions(zone
, options
)) {
1953 cerr
<< "Could not find backend willing to accept new zone configuration" << endl
;
1954 return EXIT_FAILURE
;
1956 return EXIT_SUCCESS
;
1959 static int setZoneOption(const DNSName
& zone
, const string
& type
, const string
& option
, const set
<string
>& values
)
1961 UeberBackend
B("default");
1965 if (!B
.getDomainInfo(zone
, di
)) {
1966 cerr
<< "No such zone " << zone
<< " in the database" << endl
;
1967 return EXIT_FAILURE
;
1970 CatalogInfo::CatalogType ctype
;
1971 if (type
== "producer") {
1972 ctype
= CatalogInfo::CatalogType::Producer
;
1975 ctype
= CatalogInfo::CatalogType::Consumer
;
1978 ci
.fromJson(di
.options
, ctype
);
1980 if (option
== "coo") {
1981 ci
.d_coo
= (!values
.empty() ? DNSName(*values
.begin()) : DNSName());
1983 else if (option
== "unique") {
1984 ci
.d_unique
= (!values
.empty() ? DNSName(*values
.begin()) : DNSName());
1986 else if (option
== "group") {
1987 ci
.d_group
= values
;
1990 if (!di
.backend
->setOptions(zone
, ci
.toJson())) {
1991 cerr
<< "Could not find backend willing to accept new zone configuration" << endl
;
1992 return EXIT_FAILURE
;
1995 return EXIT_SUCCESS
;
1998 static int setZoneCatalog(const DNSName
& zone
, const DNSName
& catalog
)
2000 UeberBackend
B("default");
2003 if (!B
.getDomainInfo(zone
, di
)) {
2004 cerr
<< "No such zone " << zone
<< " in the database" << endl
;
2005 return EXIT_FAILURE
;
2007 if (!di
.backend
->setCatalog(zone
, catalog
)) {
2008 cerr
<< "Could not find backend willing to accept new zone configuration" << endl
;
2009 return EXIT_FAILURE
;
2011 return EXIT_SUCCESS
;
2014 static int setZoneAccount(const DNSName
& zone
, const string
&account
)
2016 UeberBackend
B("default");
2019 if (!B
.getDomainInfo(zone
, di
)){
2020 cerr
<< "No such zone "<<zone
<<" in the database" << endl
;
2021 return EXIT_FAILURE
;
2023 if(!di
.backend
->setAccount(zone
, account
)) {
2024 cerr
<<"Could not find backend willing to accept new zone configuration"<<endl
;
2025 return EXIT_FAILURE
;
2027 return EXIT_SUCCESS
;
2030 static int setZoneKind(const DNSName
& zone
, const DomainInfo::DomainKind kind
)
2032 UeberBackend
B("default");
2035 if (!B
.getDomainInfo(zone
, di
)){
2036 cerr
<< "No such zone "<<zone
<<" in the database" << endl
;
2037 return EXIT_FAILURE
;
2039 if(!di
.backend
->setKind(zone
, kind
)) {
2040 cerr
<<"Could not find backend willing to accept new zone configuration"<<endl
;
2041 return EXIT_FAILURE
;
2043 return EXIT_SUCCESS
;
2046 static bool showZone(DNSSECKeeper
& dk
, const DNSName
& zone
, bool exportDS
= false)
2048 UeberBackend
B("default");
2051 if (!B
.getDomainInfo(zone
, di
)){
2052 cerr
<< "No such zone in the database" << endl
;
2056 if (!di
.account
.empty()) {
2057 cout
<<"This zone is owned by "<<di
.account
<<endl
;
2060 cout
<<"This is a "<<DomainInfo::getKindString(di
.kind
)<<" zone"<<endl
;
2061 if (di
.isPrimaryType()) {
2062 cout
<<"Last SOA serial number we notified: "<<di
.notified_serial
<<" ";
2064 if(B
.getSOAUncached(zone
, sd
)) {
2065 if(sd
.serial
== di
.notified_serial
)
2069 cout
<<sd
.serial
<<" (serial in the database)"<<endl
;
2072 else if (di
.isSecondaryType()) {
2073 cout
<< "Primar" << addS(di
.primaries
, "y", "ies") << ": ";
2074 for (const auto& m
: di
.primaries
)
2075 cout
<<m
.toStringWithPort()<<" ";
2078 localtime_r(&di
.last_check
, &tm
);
2081 strftime(buf
, sizeof(buf
)-1, "%a %F %H:%M:%S", &tm
);
2083 strncpy(buf
, "Never", sizeof(buf
)-1);
2084 buf
[sizeof(buf
)-1] = '\0';
2085 cout
<< "Last time we got update from primary: " << buf
<< endl
;
2087 if(B
.getSOAUncached(zone
, sd
)) {
2088 cout
<<"SOA serial in database: "<<sd
.serial
<<endl
;
2089 cout
<<"Refresh interval: "<<sd
.refresh
<<" seconds"<<endl
;
2092 cout
<<"No SOA serial found in database"<<endl
;
2096 if(!dk
.isSecuredZone(zone
)) {
2097 auto &outstream
= (exportDS
? cerr
: cout
);
2098 outstream
<< "Zone is not actively secured" << endl
;
2100 // it does not make sense to proceed here, and it might be useful
2101 // for scripts to know that something is odd here
2106 NSEC3PARAMRecordContent ns3pr
;
2107 bool narrow
= false;
2108 bool haveNSEC3
=dk
.getNSEC3PARAM(zone
, &ns3pr
, &narrow
);
2110 DNSSECKeeper::keyset_t keyset
=dk
.getKeys(zone
);
2113 std::vector
<std::string
> meta
;
2115 if (B
.getDomainMetadata(zone
, "TSIG-ALLOW-AXFR", meta
) && !meta
.empty()) {
2116 cout
<< "Zone has following allowed TSIG key(s): " << boost::join(meta
, ",") << endl
;
2120 if (B
.getDomainMetadata(zone
, "AXFR-MASTER-TSIG", meta
) && !meta
.empty()) {
2121 cout
<< "Zone uses following TSIG key(s): " << boost::join(meta
, ",") << endl
;
2124 std::map
<std::string
, std::vector
<std::string
> > metamap
;
2125 if(B
.getAllDomainMetadata(zone
, metamap
)) {
2126 cout
<<"Metadata items: ";
2131 for(const auto& m
: metamap
) {
2132 for(const auto& i
: m
.second
)
2133 cout
<< '\t' << m
.first
<<'\t' << i
<<endl
;
2139 if (dk
.isPresigned(zone
)) {
2141 cout
<<"Zone is presigned"<<endl
;
2145 vector
<DNSKEYRecordContent
> keys
;
2148 di
.backend
->lookup(QType(QType::DNSKEY
), zone
, di
.id
);
2149 while(di
.backend
->get(zr
)) {
2150 keys
.push_back(*getRR
<DNSKEYRecordContent
>(zr
.dr
));
2154 cerr
<< "No keys for zone '"<<zone
<<"'."<<endl
;
2160 cout
<<"Zone has NSEC semantics"<<endl
;
2162 cout
<<"Zone has " << (narrow
? "NARROW " : "") <<"hashed NSEC3 semantics, configuration: "<<ns3pr
.getZoneRepresentation()<<endl
;
2163 cout
<< "keys: "<<endl
;
2166 sort(keys
.begin(),keys
.end());
2167 reverse(keys
.begin(),keys
.end());
2168 for(const auto& key
: keys
) {
2169 string algname
= DNSSECKeeper::algorithm2name(key
.d_algorithm
);
2173 auto engine
= DNSCryptoKeyEngine::makeFromPublicKeyString(key
.d_algorithm
, key
.d_key
); // throws on unknown algo or bad key
2174 bits
=engine
->getBits();
2176 catch (const std::exception
& e
) {
2177 cerr
<<"Could not process key to extract metadata: "<<e
.what()<<endl
;
2180 cout
<< (key
.d_flags
== 257 ? "KSK" : "ZSK") << ", tag = " << key
.getTag() << ", algo = "<<(int)key
.d_algorithm
<< ", bits = " << bits
<< endl
;
2181 cout
<< "DNSKEY = " <<zone
.toString()<<" IN DNSKEY "<< key
.getZoneRepresentation() << "; ( " + algname
+ " ) " <<endl
;
2184 const std::string
prefix(exportDS
? "" : "DS = ");
2186 cout
<<prefix
<<zone
.toString()<<" IN DS "<<makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_SHA1
).getZoneRepresentation() << " ; ( SHA1 digest )" << endl
;
2188 cout
<<prefix
<<zone
.toString()<<" IN DS "<<makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_SHA256
).getZoneRepresentation() << " ; ( SHA256 digest )" << endl
;
2190 string output
=makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_GOST
).getZoneRepresentation();
2191 cout
<<prefix
<<zone
.toString()<<" IN DS "<<output
<< " ; ( GOST R 34.11-94 digest )" << endl
;
2196 string output
=makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_SHA384
).getZoneRepresentation();
2197 cout
<<prefix
<<zone
.toString()<<" IN DS "<<output
<< " ; ( SHA-384 digest )" << endl
;
2203 else if(keyset
.empty()) {
2204 cerr
<< "No keys for zone '"<<zone
<<"'."<<endl
;
2209 cout
<<"Zone has NSEC semantics"<<endl
;
2211 cout
<<"Zone has " << (narrow
? "NARROW " : "") <<"hashed NSEC3 semantics, configuration: "<<ns3pr
.getZoneRepresentation()<<endl
;
2212 cout
<< "keys: "<<endl
;
2215 for(const DNSSECKeeper::keyset_t::value_type
& value
: keyset
) {
2216 string algname
= DNSSECKeeper::algorithm2name(value
.first
.getAlgorithm());
2218 cout
<<"ID = "<<value
.second
.id
<<" ("<<DNSSECKeeper::keyTypeToString(value
.second
.keyType
)<<")";
2220 if (value
.first
.getKey()->getBits() < 1) {
2221 cout
<<" <key missing or defunct, perhaps you should run pdnsutil hsm create-key>" <<endl
;
2225 cout
<<", flags = "<<std::to_string(value
.first
.getFlags());
2226 cout
<<", tag = "<<value
.first
.getDNSKEY().getTag();
2227 cout
<<", algo = "<<(int)value
.first
.getAlgorithm()<<", bits = "<<value
.first
.getKey()->getBits()<<"\t"<<((int)value
.second
.active
== 1 ? " A" : "Ina")<<"ctive\t"<<(value
.second
.published
? " Published" : " Unpublished")<<" ( " + algname
+ " ) "<<endl
;
2231 if (value
.second
.keyType
== DNSSECKeeper::KSK
|| value
.second
.keyType
== DNSSECKeeper::CSK
|| ::arg().mustDo("direct-dnskey")) {
2232 cout
<<DNSSECKeeper::keyTypeToString(value
.second
.keyType
)<<" DNSKEY = "<<zone
.toString()<<" IN DNSKEY "<< value
.first
.getDNSKEY().getZoneRepresentation() << " ; ( " + algname
+ " )" << endl
;
2235 if (value
.second
.keyType
== DNSSECKeeper::KSK
|| value
.second
.keyType
== DNSSECKeeper::CSK
) {
2236 const auto &key
= value
.first
.getDNSKEY();
2237 const std::string
prefix(exportDS
? "" : "DS = ");
2239 cout
<<prefix
<<zone
.toString()<<" IN DS "<<makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_SHA1
).getZoneRepresentation() << " ; ( SHA1 digest )" << endl
;
2241 cout
<<prefix
<<zone
.toString()<<" IN DS "<<makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_SHA256
).getZoneRepresentation() << " ; ( SHA256 digest )" << endl
;
2243 string output
=makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_GOST
).getZoneRepresentation();
2244 cout
<<prefix
<<zone
.toString()<<" IN DS "<<output
<< " ; ( GOST R 34.11-94 digest )" << endl
;
2249 string output
=makeDSFromDNSKey(zone
, key
, DNSSECKeeper::DIGEST_SHA384
).getZoneRepresentation();
2250 cout
<<prefix
<<zone
.toString()<<" IN DS "<<output
<< " ; ( SHA-384 digest )" << endl
;
2257 if (!di
.options
.empty()) {
2258 cout
<< "Options:" << endl
;
2259 cout
<< di
.options
<< endl
;
2261 if (!di
.catalog
.empty()) {
2262 cout
<< "Catalog: " << endl
;
2263 cout
<< di
.catalog
<< endl
;
2268 static bool secureZone(DNSSECKeeper
& dk
, const DNSName
& zone
)
2270 // temp var for addKey
2274 string k_algo
= ::arg()["default-ksk-algorithm"];
2275 int k_size
= ::arg().asNum("default-ksk-size");
2276 string z_algo
= ::arg()["default-zsk-algorithm"];
2277 int z_size
= ::arg().asNum("default-zsk-size");
2280 throw runtime_error("KSK key size must be equal to or greater than 0");
2283 if (k_algo
.empty() && z_algo
.empty()) {
2284 throw runtime_error("Zero algorithms given for KSK+ZSK in total");
2288 throw runtime_error("ZSK key size must be equal to or greater than 0");
2291 if(dk
.isSecuredZone(zone
)) {
2292 cerr
<< "Zone '"<<zone
<<"' already secure, remove keys with pdnsutil remove-zone-key if needed"<<endl
;
2297 UeberBackend
B("default");
2298 // di.backend and B are mostly identical
2299 if(!B
.getDomainInfo(zone
, di
, false) || di
.backend
== nullptr) {
2300 cerr
<<"Can't find a zone called '"<<zone
<<"'"<<endl
;
2304 if (di
.kind
== DomainInfo::Secondary
) {
2305 cerr
<< "Warning! This is a secondary zone! If this was a mistake, please run" << endl
;
2306 cerr
<<"pdnsutil disable-dnssec "<<zone
<<" right now!"<<endl
;
2309 if (!k_algo
.empty()) { // Add a KSK
2311 cout
<< "Securing zone with key size " << k_size
<< endl
;
2313 cout
<< "Securing zone with default key size" << endl
;
2315 cout
<< "Adding " << (z_algo
.empty() ? "CSK (257)" : "KSK") << " with algorithm " << k_algo
<< endl
;
2317 int k_real_algo
= DNSSECKeeper::shorthand2algorithm(k_algo
);
2319 if (!dk
.addKey(zone
, true, k_real_algo
, id
, k_size
, true, true)) {
2320 cerr
<<"No backend was able to secure '"<<zone
<<"', most likely because no DNSSEC"<<endl
;
2321 cerr
<<"capable backends are loaded, or because the backends have DNSSEC disabled."<<endl
;
2322 cerr
<<"For the Generic SQL backends, set the 'gsqlite3-dnssec', 'gmysql-dnssec' or"<<endl
;
2323 cerr
<<"'gpgsql-dnssec' flag. Also make sure the schema has been updated for DNSSEC!"<<endl
;
2328 if (!z_algo
.empty()) {
2329 cout
<< "Adding " << (k_algo
.empty() ? "CSK (256)" : "ZSK") << " with algorithm " << z_algo
<< endl
;
2331 int z_real_algo
= DNSSECKeeper::shorthand2algorithm(z_algo
);
2333 if (!dk
.addKey(zone
, false, z_real_algo
, id
, z_size
, true, true)) {
2334 cerr
<<"No backend was able to secure '"<<zone
<<"', most likely because no DNSSEC"<<endl
;
2335 cerr
<<"capable backends are loaded, or because the backends have DNSSEC disabled."<<endl
;
2336 cerr
<<"For the Generic SQL backends, set the 'gsqlite3-dnssec', 'gmysql-dnssec' or"<<endl
;
2337 cerr
<<"'gpgsql-dnssec' flag. Also make sure the schema has been updated for DNSSEC!"<<endl
;
2342 if(!dk
.isSecuredZone(zone
)) {
2343 cerr
<<"Failed to secure zone. Is your backend dnssec enabled? (set "<<endl
;
2344 cerr
<<"gsqlite3-dnssec, or gmysql-dnssec etc). Check this first."<<endl
;
2345 cerr
<<"If you run with the BIND backend, make sure you have configured"<<endl
;
2346 cerr
<<"it to use DNSSEC with 'bind-dnssec-db=/path/fname' and"<<endl
;
2347 cerr
<<"'pdnsutil create-bind-db /path/fname'!"<<endl
;
2351 // rectifyZone(dk, zone);
2352 // showZone(dk, zone);
2353 cout
<<"Zone "<<zone
<<" secured"<<endl
;
2357 static int testSchema(DNSSECKeeper
& dk
, const DNSName
& zone
)
2359 cout
<<"Note: test-schema will try to create the zone, but it will not remove it."<<endl
;
2360 cout
<<"Please clean up after this."<<endl
;
2362 cout
<<"If this test reports an error and aborts, please check your database schema."<<endl
;
2363 cout
<<"Constructing UeberBackend"<<endl
;
2364 UeberBackend
B("default");
2365 cout
<<"Picking first backend - if this is not what you want, edit launch line!"<<endl
;
2366 DNSBackend
*db
= B
.backends
[0].get();
2367 cout
<< "Creating secondary zone " << zone
<< endl
;
2368 db
->createSecondaryDomain("127.0.0.1", zone
, "", "_testschema");
2369 cout
<< "Secondary zone created" << endl
;
2372 // di.backend and B are mostly identical
2373 if(!B
.getDomainInfo(zone
, di
) || di
.backend
== nullptr) {
2374 cout
<< "Can't find zone we just created, aborting" << endl
;
2375 return EXIT_FAILURE
;
2378 DNSResourceRecord rr
, rrget
;
2379 cout
<<"Starting transaction to feed records"<<endl
;
2380 db
->startTransaction(zone
, di
.id
);
2382 rr
.qtype
=QType::SOA
;
2387 rr
.content
="ns1.example.com. ahu.example.com. 2012081039 7200 3600 1209600 3600";
2388 cout
<<"Feeding SOA"<<endl
;
2389 db
->feedRecord(rr
, DNSName());
2390 rr
.qtype
=QType::TXT
;
2392 rr
.content
="\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"";
2393 cout
<<"Feeding overlong TXT"<<endl
;
2394 db
->feedRecord(rr
, DNSName());
2395 cout
<<"Committing"<<endl
;
2396 db
->commitTransaction();
2397 cout
<<"Querying TXT"<<endl
;
2398 db
->lookup(QType(QType::TXT
), zone
, di
.id
);
2401 DNSResourceRecord rrthrowaway
;
2402 if(db
->get(rrthrowaway
)) // should not touch rr but don't assume anything
2404 cout
<<"Expected one record, got multiple, aborting"<<endl
;
2405 return EXIT_FAILURE
;
2407 int size
=rrget
.content
.size();
2410 cout
<<"Expected 302 bytes, got "<<size
<<", aborting"<<endl
;
2411 return EXIT_FAILURE
;
2414 cout
<<"[+] content field is over 255 bytes"<<endl
;
2416 cout
<<"Dropping all records, inserting SOA+2xA"<<endl
;
2417 db
->startTransaction(zone
, di
.id
);
2419 rr
.qtype
=QType::SOA
;
2424 rr
.content
="ns1.example.com. ahu.example.com. 2012081039 7200 3600 1209600 3600";
2425 cout
<<"Feeding SOA"<<endl
;
2426 db
->feedRecord(rr
, DNSName());
2429 rr
.qname
=DNSName("_underscore")+zone
;
2430 rr
.content
="127.0.0.1";
2431 db
->feedRecord(rr
, DNSName());
2433 rr
.qname
=DNSName("bla")+zone
;
2434 cout
<<"Committing"<<endl
;
2435 db
->commitTransaction();
2437 cout
<<"Securing zone"<<endl
;
2438 secureZone(dk
, zone
);
2439 cout
<<"Rectifying zone"<<endl
;
2440 rectifyZone(dk
, zone
);
2441 cout
<<"Checking underscore ordering"<<endl
;
2442 DNSName before
, after
;
2443 db
->getBeforeAndAfterNames(di
.id
, zone
, DNSName("z")+zone
, before
, after
);
2444 cout
<<"got '"<<before
.toString()<<"' < 'z."<<zone
.toString()<<"' < '"<<after
.toString()<<"'"<<endl
;
2445 if(before
!= DNSName("_underscore")+zone
)
2447 cout
<<"before is wrong, got '"<<before
.toString()<<"', expected '_underscore."<<zone
.toString()<<"', aborting"<<endl
;
2448 return EXIT_FAILURE
;
2452 cout
<<"after is wrong, got '"<<after
.toString()<<"', expected '"<<zone
.toString()<<"', aborting"<<endl
;
2453 return EXIT_FAILURE
;
2455 cout
<<"[+] ordername sorting is correct for names starting with _"<<endl
;
2456 cout
<<"Setting low notified serial"<<endl
;
2457 db
->setNotified(di
.id
, 500);
2458 db
->getDomainInfo(zone
, di
);
2459 if(di
.notified_serial
!= 500) {
2460 cout
<<"[-] Set serial 500, got back "<<di
.notified_serial
<<", aborting"<<endl
;
2461 return EXIT_FAILURE
;
2463 cout
<<"Setting serial that needs 32 bits"<<endl
;
2465 db
->setNotified(di
.id
, 2147484148);
2466 } catch(const PDNSException
&pe
) {
2467 cout
<<"While setting serial, got error: "<<pe
.reason
<<endl
;
2468 cout
<<"aborting"<<endl
;
2469 return EXIT_FAILURE
;
2471 db
->getDomainInfo(zone
, di
);
2472 if(di
.notified_serial
!= 2147484148) {
2473 cout
<<"[-] Set serial 2147484148, got back "<<di
.notified_serial
<<", aborting"<<endl
;
2474 return EXIT_FAILURE
;
2476 cout
<<"[+] Big serials work correctly"<<endl
;
2479 cout
<< "End of tests, please remove " << zone
<< " from zones+records" << endl
;
2481 return EXIT_SUCCESS
;
2484 static int addOrSetMeta(const DNSName
& zone
, const string
& kind
, const vector
<string
>& values
, bool clobber
) {
2485 UeberBackend
B("default");
2488 if (!B
.getDomainInfo(zone
, di
)) {
2489 cerr
<< "Invalid zone '" << zone
<< "'" << endl
;
2493 vector
<string
> all_metadata
;
2496 B
.getDomainMetadata(zone
, kind
, all_metadata
);
2499 all_metadata
.insert(all_metadata
.end(), values
.begin(), values
.end());
2501 if (!B
.setDomainMetadata(zone
, kind
, all_metadata
)) {
2502 cerr
<< "Unable to set meta for '" << zone
<< "'" << endl
;
2506 cout
<< "Set '" << zone
<< "' meta " << kind
<< " = " << boost::join(all_metadata
, ", ") << endl
;
2510 // NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Clean this function up.
2511 int main(int argc
, char** argv
)
2514 po::options_description
desc("Allowed options");
2516 ("help,h", "produce help message")
2517 ("version", "show version")
2518 ("verbose,v", "be verbose")
2519 ("force", "force an action")
2520 ("config-name", po::value
<string
>()->default_value(""), "virtual configuration name")
2521 ("config-dir", po::value
<string
>()->default_value(SYSCONFDIR
), "location of pdns.conf")
2522 ("no-colors", "do not use colors in output")
2523 ("commands", po::value
<vector
<string
> >());
2525 po::positional_options_description p
;
2526 p
.add("commands", -1);
2527 po::store(po::command_line_parser(argc
, argv
).options(desc
).positional(p
).run(), g_vm
);
2530 vector
<string
> cmds
;
2532 if(g_vm
.count("commands"))
2533 cmds
= g_vm
["commands"].as
<vector
<string
> >();
2535 g_verbose
= g_vm
.count("verbose");
2537 if (g_vm
.count("version")) {
2538 cout
<<"pdnsutil "<<VERSION
<<endl
;
2542 if (cmds
.empty() || g_vm
.count("help") || cmds
.at(0) == "help") {
2543 cout
<< "Usage: \npdnsutil [options] <command> [params ..]\n"
2545 cout
<< "Commands:" << endl
;
2546 cout
<< "activate-tsig-key ZONE NAME {primary|secondary|producer|consumer}" << endl
;
2547 cout
<< " Enable TSIG authenticated AXFR using the key NAME for ZONE" << endl
;
2548 cout
<< "activate-zone-key ZONE KEY-ID Activate the key with key id KEY-ID in ZONE" << endl
;
2549 cout
<< "add-record ZONE NAME TYPE [ttl] content" << endl
;
2550 cout
<< " [content..] Add one or more records to ZONE" << endl
;
2551 cout
<< "add-autoprimary IP NAMESERVER [account]" << endl
;
2552 cout
<< " Add a new autoprimary " << endl
;
2553 cout
<< "remove-autoprimary IP NAMESERVER Remove an autoprimary" << endl
;
2554 cout
<< "list-autoprimaries List all autoprimaries" << endl
;
2555 cout
<< "add-zone-key ZONE {zsk|ksk} [BITS] [active|inactive] [published|unpublished]" << endl
;
2556 cout
<< " [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
2557 #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519)
2560 #if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
2563 cout
<< "]" << endl
;
2564 cout
<< " Add a ZSK or KSK to zone and specify algo&bits" << endl
;
2565 cout
<< "backend-cmd BACKEND CMD [CMD..] Perform one or more backend commands" << endl
;
2566 cout
<< "b2b-migrate OLD NEW Move all data from one backend to another" << endl
;
2567 cout
<< "bench-db [filename] Bench database backend with queries, one zone per line" << endl
;
2568 cout
<< "check-zone ZONE Check a zone for correctness" << endl
;
2569 cout
<< "check-all-zones [exit-on-error] Check all zones for correctness. Set exit-on-error to exit immediately" << endl
;
2570 cout
<< " after finding an error in a zone." << endl
;
2571 cout
<< "clear-zone ZONE Clear all records of a zone, but keep everything else" << endl
;
2572 cout
<< "create-bind-db FNAME Create DNSSEC db for BIND backend (bind-dnssec-db)" << endl
;
2573 cout
<< "create-secondary-zone ZONE primary-ip [primary-ip..]" << endl
;
2574 cout
<< " Create secondary zone ZONE with primary IP address primary-ip" << endl
;
2575 cout
<< "change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl
;
2576 cout
<< " Change secondary zone ZONE primary IP address to primary-ip" << endl
;
2577 cout
<< "create-zone ZONE [nsname] Create empty zone ZONE" << endl
;
2578 cout
<< "deactivate-tsig-key ZONE NAME {primary|secondary}" << endl
;
2579 cout
<< " Disable TSIG authenticated AXFR using the key NAME for ZONE" << endl
;
2580 cout
<< "deactivate-zone-key ZONE KEY-ID Deactivate the key with key id KEY-ID in ZONE" << endl
;
2581 cout
<< "delete-rrset ZONE NAME TYPE Delete named RRSET from zone" << endl
;
2582 cout
<< "delete-tsig-key NAME Delete TSIG key (warning! will not unmap key!)" << endl
;
2583 cout
<< "delete-zone ZONE Delete the zone" << endl
;
2584 cout
<< "disable-dnssec ZONE Deactivate all keys and unset PRESIGNED in ZONE" << endl
;
2585 cout
<< "edit-zone ZONE Edit zone contents using $EDITOR" << endl
;
2586 cout
<< "export-zone-dnskey ZONE KEY-ID Export to stdout the public DNSKEY described" << endl
;
2587 cout
<< "export-zone-ds ZONE Export to stdout all KSK DS records for ZONE" << endl
;
2588 cout
<< "export-zone-key ZONE KEY-ID Export to stdout the private key described" << endl
;
2589 cout
<< "export-zone-key-pem ZONE KEY-ID Export to stdout in PEM the private key described" << endl
;
2590 cout
<< "generate-tsig-key NAME ALGORITHM Generate new TSIG key" << endl
;
2591 cout
<< "generate-zone-key {zsk|ksk} [ALGORITHM] [BITS]" << endl
;
2592 cout
<< " Generate a ZSK or KSK to stdout with specified ALGORITHM and BITS" << endl
;
2593 cout
<< "get-meta ZONE [KIND ...] Get zone metadata. If no KIND given, lists all known" << endl
;
2594 cout
<< "hash-password [WORK FACTOR] Ask for a plaintext password or api key and output a hashed and salted version" << endl
;
2595 cout
<< "hash-zone-record ZONE RNAME Calculate the NSEC3 hash for RNAME in ZONE" << endl
;
2597 cout
<< "hsm assign ZONE ALGORITHM {ksk|zsk} MODULE SLOT PIN LABEL" << endl
<<
2598 " Assign a hardware signing module to a ZONE" << endl
;
2599 cout
<< "hsm create-key ZONE KEY-ID [BITS] Create a key using hardware signing module for ZONE (use assign first)" << endl
;
2600 cout
<< " BITS defaults to 2048" << endl
;
2602 cout
<< "increase-serial ZONE Increases the SOA-serial by 1. Uses SOA-EDIT" << endl
;
2603 cout
<< "import-tsig-key NAME ALGORITHM KEY Import TSIG key" << endl
;
2604 cout
<< "import-zone-key ZONE FILE Import from a file a private key, ZSK or KSK" << endl
;
2605 cout
<< " [active|inactive] [ksk|zsk] [published|unpublished] Defaults to KSK, active and published" << endl
;
2606 cout
<< "import-zone-key-pem ZONE FILE Import a private key from a PEM file" << endl
;
2607 cout
<< " ALGORITHM {ksk|zsk}" << endl
;
2608 cout
<< "ipdecrypt IP passphrase/key [key] Decrypt IP address using passphrase or base64 key" << endl
;
2609 cout
<< "ipencrypt IP passphrase/key [key] Encrypt IP address using passphrase or base64 key" << endl
;
2610 cout
<< "load-zone ZONE FILE Load ZONE from FILE, possibly creating zone or atomically" << endl
;
2611 cout
<< " replacing contents" << endl
;
2612 cout
<< "list-algorithms [with-backend] List all DNSSEC algorithms supported, optionally also listing the crypto library used" << endl
;
2613 cout
<< "list-keys [ZONE] List DNSSEC keys for ZONE. When ZONE is unset, display all keys for all active zones" << endl
;
2614 cout
<< " --verbose or -v will also include the keys for disabled or empty zones" << endl
;
2615 cout
<< "list-zone ZONE List zone contents" << endl
;
2616 cout
<< "list-all-zones [primary|secondary|native|producer|consumer]" << endl
;
2617 cout
<< " List all active zone names. --verbose or -v will also include disabled or empty zones" << endl
;
2618 cout
<< "list-member-zones CATALOG List all members of catalog zone CATALOG" << endl
;
2620 cout
<< "list-tsig-keys List all TSIG keys" << endl
;
2621 cout
<< "publish-zone-key ZONE KEY-ID Publish the zone key with key id KEY-ID in ZONE" << endl
;
2622 cout
<< "rectify-zone ZONE [ZONE ..] Fix up DNSSEC fields (order, auth)" << endl
;
2623 cout
<< "rectify-all-zones [quiet] Rectify all zones. Optionally quiet output with errors only" << endl
;
2624 cout
<< "remove-zone-key ZONE KEY-ID Remove key with KEY-ID from ZONE" << endl
;
2625 cout
<< "replace-rrset ZONE NAME TYPE [ttl] Replace named RRSET from zone" << endl
;
2626 cout
<< " content [content..]" << endl
;
2627 cout
<< "secure-all-zones [increase-serial] Secure all zones without keys" << endl
;
2628 cout
<< "secure-zone ZONE [ZONE ..] Add DNSSEC to zone ZONE" << endl
;
2629 cout
<< "set-kind ZONE KIND Change the kind of ZONE to KIND (primary, secondary, native, producer, consumer)" << endl
;
2630 cout
<< "set-options-json ZONE JSON Change the options of ZONE to JSON" << endl
;
2631 cout
<< "set-option ZONE Set or remove an option for ZONE Providing an empty value removes an option" << endl
;
2632 cout
<< " [producer|consumer]" << endl
;
2633 cout
<< " [coo|unique|group] VALUE" << endl
;
2634 cout
<< " [VALUE ...]" << endl
;
2635 cout
<< "set-catalog ZONE CATALOG Change the catalog of ZONE to CATALOG" << endl
;
2636 cout
<< "set-account ZONE ACCOUNT Change the account (owner) of ZONE to ACCOUNT" << endl
;
2637 cout
<< "set-nsec3 ZONE ['PARAMS' [narrow]] Enable NSEC3 with PARAMS. Optionally narrow" << endl
;
2638 cout
<< "set-presigned ZONE Use presigned RRSIGs from storage" << endl
;
2639 cout
<< "set-publish-cdnskey ZONE [delete] Enable sending CDNSKEY responses for ZONE. Add 'delete' to publish a CDNSKEY with a" << endl
;
2640 cout
<< " DNSSEC delete algorithm" << endl
;
2641 cout
<< "set-publish-cds ZONE [DIGESTALGOS] Enable sending CDS responses for ZONE, using DIGESTALGOS as signature algorithms" << endl
;
2642 cout
<< " DIGESTALGOS should be a comma separated list of numbers, it is '2' by default" << endl
;
2643 cout
<< "add-meta ZONE KIND VALUE Add zone metadata, this adds to the existing KIND" << endl
;
2644 cout
<< " [VALUE ...]" << endl
;
2645 cout
<< "set-meta ZONE KIND [VALUE] [VALUE] Set zone metadata, optionally providing a value. *No* value clears meta" << endl
;
2646 cout
<< " Note - this will replace all metadata records of KIND!" << endl
;
2647 cout
<< "show-zone ZONE Show DNSSEC (public) key details about a zone" << endl
;
2648 cout
<< "unpublish-zone-key ZONE KEY-ID Unpublish the zone key with key id KEY-ID in ZONE" << endl
;
2649 cout
<< "unset-nsec3 ZONE Switch back to NSEC" << endl
;
2650 cout
<< "unset-presigned ZONE No longer use presigned RRSIGs" << endl
;
2651 cout
<< "unset-publish-cdnskey ZONE Disable sending CDNSKEY responses for ZONE" << endl
;
2652 cout
<< "unset-publish-cds ZONE Disable sending CDS responses for ZONE" << endl
;
2653 cout
<< "test-schema ZONE Test DB schema - will create ZONE" << endl
;
2654 cout
<< "raw-lua-from-content TYPE CONTENT Display record contents in a form suitable for dnsdist's `SpoofRawAction`" << endl
;
2655 cout
<< "zonemd-verify-file ZONE FILE Validate ZONEMD for ZONE" << endl
;
2656 cout
<< "lmdb-get-backend-version Get schema version supported by backend" << endl
;
2657 cout
<< desc
<< endl
;
2662 loadMainConfig(g_vm
["config-dir"].as
<string
>());
2664 if (cmds
.at(0) == "lmdb-get-backend-version") {
2665 cout
<< "5" << endl
; // FIXME this should reuse the constant from lmdbbackend but that is currently a #define in a .cc
2668 if (cmds
.at(0) == "test-algorithm") {
2669 if(cmds
.size() != 2) {
2670 cerr
<< "Syntax: pdnsutil test-algorithm algonum"<<endl
;
2673 if (testAlgorithm(pdns::checked_stoi
<int>(cmds
.at(1))))
2678 if (cmds
.at(0) == "ipencrypt" || cmds
.at(0) == "ipdecrypt") {
2679 if (cmds
.size() < 3 || (cmds
.size() == 4 && cmds
.at(3) != "key")) {
2680 cerr
<<"Syntax: pdnsutil [ipencrypt|ipdecrypt] IP passphrase [key]"<<endl
;
2683 #ifdef HAVE_IPCIPHER
2685 if(cmds
.size()==4) {
2686 if (B64Decode(cmds
.at(2), key
) < 0) {
2687 cerr
<< "Could not parse '" << cmds
.at(3) << "' as base64" << endl
;
2692 key
= makeIPCipherKey(cmds
.at(2));
2694 exit(xcryptIP(cmds
.at(0), cmds
.at(1), key
));
2696 cerr
<<cmds
.at(0)<<" requires ipcipher support which is not available"<<endl
;
2698 #endif /* HAVE_IPCIPHER */
2701 if (cmds
.at(0) == "test-algorithms") {
2702 if (testAlgorithms())
2707 if (cmds
.at(0) == "list-algorithms") {
2708 if ((cmds
.size() == 2 && cmds
.at(1) != "with-backend") || cmds
.size() > 2) {
2709 cerr
<<"Syntax: pdnsutil list-algorithms [with-backend]"<<endl
;
2713 cout
<<"DNSKEY algorithms supported by this installation of PowerDNS:"<<endl
;
2715 auto algosWithBackend
= DNSCryptoKeyEngine::listAllAlgosWithBackend();
2716 for (const auto& algoWithBackend
: algosWithBackend
){
2717 string algoName
= DNSSECKeeper::algorithm2name(algoWithBackend
.first
);
2718 cout
<<std::to_string(algoWithBackend
.first
)<<" - "<<algoName
;
2719 if (cmds
.size() == 2 && cmds
.at(1) == "with-backend")
2720 cout
<<" using "<<algoWithBackend
.second
;
2728 if (cmds
.at(0) == "create-bind-db") {
2730 if(cmds
.size() != 2) {
2731 cerr
<< "Syntax: pdnsutil create-bind-db FNAME"<<endl
;
2735 SSQLite3
db(cmds
.at(1), "", true); // create=ok
2736 vector
<string
> statements
;
2737 stringtok(statements
, sqlCreate
, ";");
2738 for(const string
& statement
: statements
) {
2739 db
.execute(statement
);
2742 catch(SSqlException
& se
) {
2743 throw PDNSException("Error creating database in BIND backend: "+se
.txtReason());
2747 cerr
<<"bind-dnssec-db requires building PowerDNS with SQLite3"<<endl
;
2752 if (cmds
.at(0) == "raw-lua-from-content") {
2753 if (cmds
.size() < 3) {
2754 cerr
<<"Usage: raw-lua-from-content TYPE CONTENT"<<endl
;
2758 // DNSResourceRecord rr;
2759 // rr.qtype = DNSRecordContent::TypeToNumber(cmds.at(1));
2760 // rr.content = cmds.at(2);
2761 auto drc
= DNSRecordContent::make(DNSRecordContent::TypeToNumber(cmds
.at(1)), QClass::IN
, cmds
.at(2));
2762 cout
<<makeLuaString(drc
->serialize(DNSName(), true))<<endl
;
2766 else if (cmds
.at(0) == "hash-password") {
2767 uint64_t workFactor
= CredentialsHolder::s_defaultWorkFactor
;
2768 if (cmds
.size() > 1) {
2770 pdns::checked_stoi_into(workFactor
, cmds
.at(1));
2772 catch (const std::exception
& e
) {
2773 cerr
<<"Unable to parse the supplied work factor: "<<e
.what()<<endl
;
2778 auto password
= CredentialsHolder::readFromTerminal();
2781 cout
<<hashPassword(password
.getString(), workFactor
, CredentialsHolder::s_defaultParallelFactor
, CredentialsHolder::s_defaultBlockSize
)<<endl
;
2782 return EXIT_SUCCESS
;
2784 catch (const std::exception
& e
) {
2785 cerr
<<"Error while hashing the supplied password: "<<e
.what()<<endl
;
2790 if(cmds
[0] == "zonemd-verify-file") {
2791 if(cmds
.size() < 3) {
2792 cerr
<<"Syntax: pdnsutil zonemd-verify-file ZONE FILENAME"<<endl
;
2798 auto ret
= zonemdVerifyFile(DNSName(cmds
[1]), cmds
[2]);
2804 if (cmds
.at(0) == "test-schema") {
2805 if(cmds
.size() != 2) {
2806 cerr
<< "Syntax: pdnsutil test-schema ZONE"<<endl
;
2809 return testSchema(dk
, DNSName(cmds
.at(1)));
2811 if (cmds
.at(0) == "rectify-zone") {
2812 if(cmds
.size() < 2) {
2813 cerr
<< "Syntax: pdnsutil rectify-zone ZONE [ZONE..]"<<endl
;
2816 unsigned int exitCode
= 0;
2817 for(unsigned int n
= 1; n
< cmds
.size(); ++n
)
2818 if (!rectifyZone(dk
, DNSName(cmds
.at(n
))))
2822 else if (cmds
.at(0) == "rectify-all-zones") {
2823 bool quiet
= (cmds
.size() >= 2 && cmds
.at(1) == "quiet");
2824 if (!rectifyAllZones(dk
, quiet
)) {
2828 else if (cmds
.at(0) == "check-zone") {
2829 if(cmds
.size() != 2) {
2830 cerr
<< "Syntax: pdnsutil check-zone ZONE"<<endl
;
2833 UeberBackend
B("default");
2834 return checkZone(dk
, B
, DNSName(cmds
.at(1)));
2836 else if (cmds
.at(0) == "bench-db") {
2837 dbBench(cmds
.size() > 1 ? cmds
.at(1) : "");
2839 else if (cmds
.at(0) == "check-all-zones") {
2840 bool exitOnError
= ((cmds
.size() >= 2 ? cmds
.at(1) : "") == "exit-on-error");
2841 return checkAllZones(dk
, exitOnError
);
2843 else if (cmds
.at(0) == "list-all-zones") {
2844 if (cmds
.size() > 2) {
2845 cerr
<< "Syntax: pdnsutil list-all-zones [primary|secondary|native|producer|consumer]" << endl
;
2848 if (cmds
.size() == 2)
2849 return listAllZones(cmds
.at(1));
2850 return listAllZones();
2852 else if (cmds
.at(0) == "list-member-zones") {
2853 if (cmds
.size() != 2) {
2854 cerr
<< "Syntax: pdnsutil list-member-zones CATALOG" << endl
;
2857 return listMemberZones(cmds
.at(1));
2859 else if (cmds
.at(0) == "test-zone") {
2860 cerr
<< "Did you mean check-zone?"<<endl
;
2863 else if (cmds
.at(0) == "test-all-zones") {
2864 cerr
<< "Did you mean check-all-zones?"<<endl
;
2868 else if(cmds
.at(0) == "signing-server" )
2872 else if(cmds
.at(0) == "signing-secondary")
2874 launchSigningService(0);
2877 else if (cmds
.at(0) == "test-speed") {
2878 if(cmds
.size() < 2) {
2879 cerr
<< "Syntax: pdnsutil test-speed numcores [signing-server]"<<endl
;
2882 testSpeed(DNSName(cmds
.at(1)), (cmds
.size() > 3) ? cmds
.at(3) : "", pdns::checked_stoi
<int>(cmds
.at(2)));
2884 else if (cmds
.at(0) == "verify-crypto") {
2885 if(cmds
.size() != 2) {
2886 cerr
<< "Syntax: pdnsutil verify-crypto FILE"<<endl
;
2889 verifyCrypto(cmds
.at(1));
2891 else if (cmds
.at(0) == "show-zone") {
2892 if(cmds
.size() != 2) {
2893 cerr
<< "Syntax: pdnsutil show-zone ZONE"<<endl
;
2896 if (!showZone(dk
, DNSName(cmds
.at(1))))
2899 else if (cmds
.at(0) == "export-zone-ds") {
2900 if(cmds
.size() != 2) {
2901 cerr
<< "Syntax: pdnsutil export-zone-ds ZONE"<<endl
;
2904 if (!showZone(dk
, DNSName(cmds
.at(1)), true))
2907 else if (cmds
.at(0) == "disable-dnssec") {
2908 if(cmds
.size() != 2) {
2909 cerr
<< "Syntax: pdnsutil disable-dnssec ZONE"<<endl
;
2912 DNSName
zone(cmds
.at(1));
2913 if(!disableDNSSECOnZone(dk
, zone
)) {
2914 cerr
<< "Cannot disable DNSSEC on " << zone
<< endl
;
2918 else if (cmds
.at(0) == "activate-zone-key") {
2919 if(cmds
.size() != 3) {
2920 cerr
<< "Syntax: pdnsutil activate-zone-key ZONE KEY-ID"<<endl
;
2923 DNSName
zone(cmds
.at(1));
2924 unsigned int id
= atoi(cmds
.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
2927 cerr
<< "Invalid KEY-ID '" << cmds
.at(2) << "'" << endl
;
2931 dk
.getKeyById(zone
, id
);
2932 } catch (std::exception
& e
) {
2933 cerr
<<e
.what()<<endl
;
2936 if (!dk
.activateKey(zone
, id
)) {
2937 cerr
<<"Activation of key failed"<<endl
;
2942 else if (cmds
.at(0) == "deactivate-zone-key") {
2943 if(cmds
.size() != 3) {
2944 cerr
<< "Syntax: pdnsutil deactivate-zone-key ZONE KEY-ID"<<endl
;
2947 DNSName
zone(cmds
.at(1));
2948 auto id
= pdns::checked_stoi
<unsigned int>(cmds
.at(2));
2951 cerr
<<"Invalid KEY-ID"<<endl
;
2955 dk
.getKeyById(zone
, id
);
2956 } catch (std::exception
& e
) {
2957 cerr
<<e
.what()<<endl
;
2960 if (!dk
.deactivateKey(zone
, id
)) {
2961 cerr
<<"Deactivation of key failed"<<endl
;
2966 else if (cmds
.at(0) == "publish-zone-key") {
2967 if(cmds
.size() != 3) {
2968 cerr
<< "Syntax: pdnsutil publish-zone-key ZONE KEY-ID"<<endl
;
2971 DNSName
zone(cmds
.at(1));
2972 unsigned int id
= atoi(cmds
.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
2975 cerr
<< "Invalid KEY-ID '" << cmds
.at(2) << "'" << endl
;
2979 dk
.getKeyById(zone
, id
);
2980 } catch (std::exception
& e
) {
2981 cerr
<<e
.what()<<endl
;
2984 if (!dk
.publishKey(zone
, id
)) {
2985 cerr
<<"Publishing of key failed"<<endl
;
2990 else if (cmds
.at(0) == "unpublish-zone-key") {
2991 if(cmds
.size() != 3) {
2992 cerr
<< "Syntax: pdnsutil unpublish-zone-key ZONE KEY-ID"<<endl
;
2995 DNSName
zone(cmds
.at(1));
2996 unsigned int id
= atoi(cmds
.at(2).c_str()); // if you make this pdns::checked_stoi, the error gets worse
2999 cerr
<< "Invalid KEY-ID '" << cmds
.at(2) << "'" << endl
;
3003 dk
.getKeyById(zone
, id
);
3004 } catch (std::exception
& e
) {
3005 cerr
<<e
.what()<<endl
;
3008 if (!dk
.unpublishKey(zone
, id
)) {
3009 cerr
<<"Unpublishing of key failed"<<endl
;
3015 else if (cmds
.at(0) == "add-zone-key") {
3016 if(cmds
.size() < 3 ) {
3017 cerr
<< "Syntax: pdnsutil add-zone-key ZONE [zsk|ksk] [BITS] [active|inactive] [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
3018 #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519)
3021 #if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
3026 cerr
<< "If zsk|ksk is omitted, add-zone-key makes a key with flags 256 (a 'ZSK')."<<endl
;
3029 DNSName
zone(cmds
.at(1));
3031 UeberBackend
B("default");
3034 if (!B
.getDomainInfo(zone
, di
)){
3035 cerr
<< "No such zone in the database" << endl
;
3039 // need to get algorithm, bits & ksk or zsk from commandline
3040 bool keyOrZone
=false;
3043 int algorithm
=DNSSECKeeper::ECDSA256
;
3045 bool published
=true;
3046 for(unsigned int n
=2; n
< cmds
.size(); ++n
) {
3047 if (pdns_iequals(cmds
.at(n
), "zsk"))
3049 else if (pdns_iequals(cmds
.at(n
), "ksk"))
3051 else if ((tmp_algo
= DNSSECKeeper::shorthand2algorithm(cmds
.at(n
))) > 0) {
3052 algorithm
= tmp_algo
;
3054 else if (pdns_iequals(cmds
.at(n
), "active")) {
3057 else if (pdns_iequals(cmds
.at(n
), "inactive") || pdns_iequals(cmds
.at(n
), "passive")) { // 'passive' eventually needs to be removed
3060 else if (pdns_iequals(cmds
.at(n
), "published")) {
3063 else if (pdns_iequals(cmds
.at(n
), "unpublished")) {
3066 else if (pdns::checked_stoi
<int>(cmds
.at(n
)) != 0) {
3067 pdns::checked_stoi_into(bits
, cmds
.at(n
));
3070 cerr
<< "Unknown algorithm, key flag or size '" << cmds
.at(n
) << "'" << endl
;
3071 return EXIT_FAILURE
;
3075 if (!dk
.addKey(zone
, keyOrZone
, algorithm
, id
, bits
, active
, published
)) {
3076 cerr
<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl
;
3079 cerr
<<"Added a " << (keyOrZone
? "KSK" : "ZSK")<<" with algorithm = "<<algorithm
<<", active="<<active
<<endl
;
3081 cerr
<<"Requested specific key size of "<<bits
<<" bits"<<endl
;
3083 cerr
<<std::to_string(id
)<<": Key was added, but backend does not support returning of key id"<<endl
;
3084 } else if (id
< -1) {
3085 cerr
<<std::to_string(id
)<<": Key was added, but there was a failure while returning the key id"<<endl
;
3087 cout
<<std::to_string(id
)<<endl
;
3091 else if (cmds
.at(0) == "remove-zone-key") {
3092 if(cmds
.size() < 3) {
3093 cerr
<<"Syntax: pdnsutil remove-zone-key ZONE KEY-ID"<<endl
;
3096 DNSName
zone(cmds
.at(1));
3097 auto id
= pdns::checked_stoi
<unsigned int>(cmds
.at(2));
3098 if (!dk
.removeKey(zone
, id
)) {
3099 cerr
<<"Cannot remove key " << id
<< " from " << zone
<<endl
;
3104 else if (cmds
.at(0) == "delete-zone") {
3105 if(cmds
.size() != 2) {
3106 cerr
<<"Syntax: pdnsutil delete-zone ZONE"<<endl
;
3109 return deleteZone(DNSName(cmds
.at(1)));
3111 else if (cmds
.at(0) == "create-zone") {
3112 if(cmds
.size() != 2 && cmds
.size()!=3 ) {
3113 cerr
<<"Syntax: pdnsutil create-zone ZONE [nsname]"<<endl
;
3116 return createZone(DNSName(cmds
.at(1)), cmds
.size() > 2 ? DNSName(cmds
.at(2)) : DNSName());
3118 else if (cmds
.at(0) == "create-secondary-zone") {
3119 if(cmds
.size() < 3 ) {
3120 cerr
<< "Syntax: pdnsutil create-secondary-zone ZONE primary-ip [primary-ip..]" << endl
;
3123 return createSecondaryZone(cmds
);
3125 else if (cmds
.at(0) == "change-secondary-zone-primary") {
3126 if(cmds
.size() < 3 ) {
3127 cerr
<< "Syntax: pdnsutil change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl
;
3130 return changeSecondaryZonePrimary(cmds
);
3132 else if (cmds
.at(0) == "add-record") {
3133 if(cmds
.size() < 5) {
3134 cerr
<<R
"(Syntax: pdnsutil add-record ZONE name type [ttl] "content
" ["content
"...])"<<endl
;
3137 return addOrReplaceRecord(true, cmds
);
3139 else if (cmds
.at(0) == "add-autoprimary" || cmds
.at(0) == "add-autoprimary") {
3140 if(cmds
.size() < 3) {
3141 cerr
<< "Syntax: pdnsutil add-autoprimary IP NAMESERVER [account]" << endl
;
3144 exit(addAutoPrimary(cmds
.at(1), cmds
.at(2), cmds
.size() > 3 ? cmds
.at(3) : ""));
3146 else if (cmds
.at(0) == "remove-autoprimary") {
3147 if(cmds
.size() < 3) {
3148 cerr
<< "Syntax: pdnsutil remove-autoprimary IP NAMESERVER" << endl
;
3151 exit(removeAutoPrimary(cmds
.at(1), cmds
.at(2)));
3153 else if (cmds
.at(0) == "list-autoprimaries") {
3154 exit(listAutoPrimaries());
3156 else if (cmds
.at(0) == "replace-rrset") {
3157 if(cmds
.size() < 5) {
3158 cerr
<<R
"(Syntax: pdnsutil replace-rrset ZONE name type [ttl] "content
" ["content
"...])"<<endl
;
3161 return addOrReplaceRecord(false , cmds
);
3163 else if (cmds
.at(0) == "delete-rrset") {
3164 if(cmds
.size() != 4) {
3165 cerr
<<"Syntax: pdnsutil delete-rrset ZONE name type"<<endl
;
3168 return deleteRRSet(cmds
.at(1), cmds
.at(2), cmds
.at(3));
3170 else if (cmds
.at(0) == "list-zone") {
3171 if(cmds
.size() != 2) {
3172 cerr
<<"Syntax: pdnsutil list-zone ZONE"<<endl
;
3175 if (cmds
.at(1) == ".")
3178 return listZone(DNSName(cmds
.at(1)));
3180 else if (cmds
.at(0) == "edit-zone") {
3181 if(cmds
.size() != 2) {
3182 cerr
<<"Syntax: pdnsutil edit-zone ZONE"<<endl
;
3185 if (cmds
.at(1) == ".")
3188 PDNSColors
col(g_vm
.count("no-colors"));
3189 return editZone(DNSName(cmds
.at(1)), col
);
3191 else if (cmds
.at(0) == "clear-zone") {
3192 if(cmds
.size() != 2) {
3193 cerr
<<"Syntax: pdnsutil clear-zone ZONE"<<endl
;
3196 if (cmds
.at(1) == ".")
3199 return clearZone(DNSName(cmds
.at(1)));
3201 else if (cmds
.at(0) == "list-keys") {
3202 if(cmds
.size() > 2) {
3203 cerr
<<"Syntax: pdnsutil list-keys [ZONE]"<<endl
;
3207 if (cmds
.size() == 2) {
3210 return listKeys(zname
, dk
);
3212 else if (cmds
.at(0) == "load-zone") {
3213 if(cmds
.size() < 3) {
3214 cerr
<<"Syntax: pdnsutil load-zone ZONE FILENAME [ZONE FILENAME] .."<<endl
;
3217 if (cmds
.at(1) == ".")
3220 for(size_t n
=1; n
+ 2 <= cmds
.size(); n
+=2) {
3221 auto ret
= loadZone(DNSName(cmds
.at(n
)), cmds
.at(n
+ 1));
3226 else if (cmds
.at(0) == "secure-zone") {
3227 if(cmds
.size() < 2) {
3228 cerr
<< "Syntax: pdnsutil secure-zone ZONE"<<endl
;
3231 vector
<DNSName
> mustRectify
;
3232 unsigned int zoneErrors
=0;
3233 for(unsigned int n
= 1; n
< cmds
.size(); ++n
) {
3234 DNSName
zone(cmds
.at(n
));
3235 dk
.startTransaction(zone
, -1);
3236 if(secureZone(dk
, zone
)) {
3237 mustRectify
.push_back(zone
);
3241 dk
.commitTransaction();
3244 for(const auto& zone
: mustRectify
)
3245 rectifyZone(dk
, zone
);
3252 else if (cmds
.at(0) == "secure-all-zones") {
3253 if (cmds
.size() >= 2 && !pdns_iequals(cmds
.at(1), "increase-serial")) {
3254 cerr
<< "Syntax: pdnsutil secure-all-zones [increase-serial]"<<endl
;
3258 UeberBackend
B("default");
3260 vector
<DomainInfo
> domainInfo
;
3261 B
.getAllDomains(&domainInfo
, false, false);
3263 unsigned int zonesSecured
=0, zoneErrors
=0;
3264 for(const DomainInfo
& di
: domainInfo
) {
3265 if(!dk
.isSecuredZone(di
.zone
)) {
3266 cout
<<"Securing "<<di
.zone
<<": ";
3267 if (secureZone(dk
, di
.zone
)) {
3269 if (cmds
.size() == 2) {
3270 if (!increaseSerial(di
.zone
, dk
))
3279 cout
<<"Secured: "<<zonesSecured
<<" zones. Errors: "<<zoneErrors
<<endl
;
3286 else if (cmds
.at(0) == "set-kind") {
3287 if(cmds
.size() != 3) {
3288 cerr
<<"Syntax: pdnsutil set-kind ZONE KIND"<<endl
;
3291 DNSName
zone(cmds
.at(1));
3292 auto kind
= DomainInfo::stringToKind(cmds
.at(2));
3293 return setZoneKind(zone
, kind
);
3295 else if (cmds
.at(0) == "set-options-json") {
3296 if (cmds
.size() != 3) {
3297 cerr
<< "Syntax: pdnsutil set-options ZONE VALUE" << endl
;
3298 return EXIT_FAILURE
;
3302 if (!cmds
.at(2).empty()) {
3304 json11::Json doc
= json11::Json::parse(cmds
.at(2), err
);
3305 if (doc
.is_null()) {
3306 cerr
<< "Parsing of JSON document failed:" << err
<< endl
;
3307 return EXIT_FAILURE
;
3311 DNSName
zone(cmds
.at(1));
3313 return setZoneOptionsJson(zone
, cmds
.at(2));
3315 else if (cmds
.at(0) == "set-option") {
3316 if (cmds
.size() < 5 || (cmds
.size() > 5 && (cmds
.at(3) != "group"))) {
3317 cerr
<< "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]1" << endl
;
3318 return EXIT_FAILURE
;
3321 if ((cmds
.at(2) != "producer" && cmds
.at(2) != "consumer") || (cmds
.at(3) != "coo" && cmds
.at(3) != "unique" && cmds
.at(3) != "group")) {
3322 cerr
<< "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]" << endl
;
3323 return EXIT_FAILURE
;
3326 DNSName
zone(cmds
.at(1));
3328 for (unsigned int n
= 4; n
< cmds
.size(); ++n
) {
3329 if (!cmds
.at(n
).empty()) {
3330 values
.insert(cmds
.at(n
));
3334 return setZoneOption(zone
, cmds
.at(2), cmds
.at(3), values
);
3336 else if (cmds
.at(0) == "set-catalog") {
3337 if (cmds
.size() != 3) {
3338 cerr
<< "Syntax: pdnsutil set-catalog ZONE CATALOG" << endl
;
3341 DNSName
zone(cmds
.at(1));
3342 DNSName catalog
; // Create an empty DNSName()
3343 if (!cmds
.at(2).empty()) {
3344 catalog
= DNSName(cmds
.at(2));
3346 return setZoneCatalog(zone
, catalog
);
3348 else if (cmds
.at(0) == "set-account") {
3349 if(cmds
.size() != 3) {
3350 cerr
<<"Syntax: pdnsutil set-account ZONE ACCOUNT"<<endl
;
3353 DNSName
zone(cmds
.at(1));
3354 return setZoneAccount(zone
, cmds
.at(2));
3356 else if (cmds
.at(0) == "set-nsec3") {
3357 if(cmds
.size() < 2) {
3358 cerr
<<"Syntax: pdnsutil set-nsec3 ZONE 'params' [narrow]"<<endl
;
3361 string nsec3params
= cmds
.size() > 2 ? cmds
.at(2) : "1 0 0 -";
3362 bool narrow
= cmds
.size() > 3 && cmds
.at(3) == "narrow";
3363 NSEC3PARAMRecordContent
ns3pr(nsec3params
);
3365 DNSName
zone(cmds
.at(1));
3366 if (zone
.wirelength() > 222) {
3367 cerr
<<"Cannot enable NSEC3 for " << zone
<< " as it is too long (" << zone
.wirelength() << " bytes, maximum is 222 bytes)"<<endl
;
3370 if(ns3pr
.d_algorithm
!= 1) {
3371 cerr
<<"NSEC3PARAM algorithm set to '"<<std::to_string(ns3pr
.d_algorithm
)<<"', but '1' is the only valid value"<<endl
;
3372 return EXIT_FAILURE
;
3374 if (! dk
.setNSEC3PARAM(zone
, ns3pr
, narrow
)) {
3375 cerr
<<"Cannot set NSEC3 param for " << zone
<< endl
;
3380 cerr
<<"NSEC3 set, ";
3382 cerr
<<"NSEC3 (opt-out) set, ";
3384 if(dk
.isSecuredZone(zone
))
3385 cerr
<<"Done, please rectify your zone if your backend needs it (or reload it if you are using the bindbackend)"<<endl
;
3387 cerr
<<"Done, please secure and rectify your zone (or reload it if you are using the bindbackend)"<<endl
;
3391 else if (cmds
.at(0) == "set-presigned") {
3392 if(cmds
.size() < 2) {
3393 cerr
<<"Syntax: pdnsutil set-presigned ZONE"<<endl
;
3396 if (!dk
.setPresigned(DNSName(cmds
.at(1)))) {
3397 cerr
<< "Could not set presigned for " << cmds
.at(1) << " (is DNSSEC enabled in your backend?)" << endl
;
3402 else if (cmds
.at(0) == "set-publish-cdnskey") {
3403 if (cmds
.size() < 2 || (cmds
.size() == 3 && cmds
.at(2) != "delete")) {
3404 cerr
<<"Syntax: pdnsutil set-publish-cdnskey ZONE [delete]"<<endl
;
3407 if (!dk
.setPublishCDNSKEY(DNSName(cmds
.at(1)), (cmds
.size() == 3 && cmds
.at(2) == "delete"))) {
3408 cerr
<< "Could not set publishing for CDNSKEY records for " << cmds
.at(1) << endl
;
3413 else if (cmds
.at(0) == "set-publish-cds") {
3414 if(cmds
.size() < 2) {
3415 cerr
<<"Syntax: pdnsutil set-publish-cds ZONE [DIGESTALGOS]"<<endl
;
3419 // If DIGESTALGOS is unset
3420 if(cmds
.size() == 2)
3421 cmds
.push_back("2");
3423 if (!dk
.setPublishCDS(DNSName(cmds
.at(1)), cmds
.at(2))) {
3424 cerr
<< "Could not set publishing for CDS records for " << cmds
.at(1) << endl
;
3429 else if (cmds
.at(0) == "unset-presigned") {
3430 if(cmds
.size() < 2) {
3431 cerr
<<"Syntax: pdnsutil unset-presigned ZONE"<<endl
;
3434 if (!dk
.unsetPresigned(DNSName(cmds
.at(1)))) {
3435 cerr
<< "Could not unset presigned on for " << cmds
.at(1) << endl
;
3440 else if (cmds
.at(0) == "unset-publish-cdnskey") {
3441 if(cmds
.size() < 2) {
3442 cerr
<<"Syntax: pdnsutil unset-publish-cdnskey ZONE"<<endl
;
3445 if (!dk
.unsetPublishCDNSKEY(DNSName(cmds
.at(1)))) {
3446 cerr
<< "Could not unset publishing for CDNSKEY records for " << cmds
.at(1) << endl
;
3451 else if (cmds
.at(0) == "unset-publish-cds") {
3452 if(cmds
.size() < 2) {
3453 cerr
<<"Syntax: pdnsutil unset-publish-cds ZONE"<<endl
;
3456 if (!dk
.unsetPublishCDS(DNSName(cmds
.at(1)))) {
3457 cerr
<< "Could not unset publishing for CDS records for " << cmds
.at(1) << endl
;
3462 else if(cmds
.at(0) == "hash-password") {
3463 if (cmds
.size() < 2) {
3464 cerr
<<"Syntax: pdnsutil hash-password PASSWORD"<<endl
;
3467 cout
<<hashPassword(cmds
.at(1))<<endl
;
3470 else if (cmds
.at(0) == "hash-zone-record") {
3471 if(cmds
.size() < 3) {
3472 cerr
<<"Syntax: pdnsutil hash-zone-record ZONE RNAME"<<endl
;
3475 DNSName
zone(cmds
.at(1));
3476 DNSName
record(cmds
.at(2));
3477 NSEC3PARAMRecordContent ns3pr
;
3478 bool narrow
= false;
3479 if(!dk
.getNSEC3PARAM(zone
, &ns3pr
, &narrow
)) {
3480 cerr
<<"The '"<<zone
<<"' zone does not use NSEC3"<<endl
;
3484 cerr
<<"The '"<<zone
<<"' zone uses narrow NSEC3, but calculating hash anyhow"<<endl
;
3487 cout
<<toBase32Hex(hashQNameWithSalt(ns3pr
, record
))<<endl
;
3489 else if (cmds
.at(0) == "unset-nsec3") {
3490 if(cmds
.size() < 2) {
3491 cerr
<<"Syntax: pdnsutil unset-nsec3 ZONE"<<endl
;
3494 if (!dk
.unsetNSEC3PARAM(DNSName(cmds
.at(1)))) {
3495 cerr
<< "Cannot unset NSEC3 param for " << cmds
.at(1) << endl
;
3498 cerr
<<"Done, please rectify your zone if your backend needs it (or reload it if you are using the bindbackend)"<<endl
;
3502 else if (cmds
.at(0) == "export-zone-key") {
3503 if (cmds
.size() < 3) {
3504 cerr
<< "Syntax: pdnsutil export-zone-key ZONE KEY-ID" << endl
;
3508 string zone
= cmds
.at(1);
3509 auto id
= pdns::checked_stoi
<unsigned int>(cmds
.at(2));
3510 DNSSECPrivateKey dpk
= dk
.getKeyById(DNSName(zone
), id
);
3511 cout
<< dpk
.getKey()->convertToISC() << endl
;
3513 else if (cmds
.at(0) == "export-zone-key-pem") {
3514 if (cmds
.size() < 3) {
3515 cerr
<< "Syntax: pdnsutil export-zone-key-pem ZONE KEY-ID" << endl
;
3519 string zone
= cmds
.at(1);
3520 auto id
= pdns::checked_stoi
<unsigned int>(cmds
.at(2));
3521 DNSSECPrivateKey dpk
= dk
.getKeyById(DNSName(zone
), id
);
3522 dpk
.getKey()->convertToPEMFile(*stdout
);
3524 else if (cmds
.at(0) == "increase-serial") {
3525 if (cmds
.size() < 2) {
3526 cerr
<< "Syntax: pdnsutil increase-serial ZONE" << endl
;
3529 return increaseSerial(DNSName(cmds
.at(1)), dk
);
3531 else if (cmds
.at(0) == "import-zone-key-pem") {
3532 if (cmds
.size() < 4) {
3533 cerr
<< "Syntax: pdnsutil import-zone-key-pem ZONE FILE ALGORITHM {ksk|zsk}" << endl
;
3537 const string zone
= cmds
.at(1);
3538 const string filename
= cmds
.at(2);
3539 const auto algorithm
= pdns::checked_stoi
<unsigned int>(cmds
.at(3));
3542 std::unique_ptr
<std::FILE, decltype(&std::fclose
)> fp
{std::fopen(filename
.c_str(), "r"), &std::fclose
};
3543 if (fp
== nullptr) {
3544 auto errMsg
= pdns::getMessageFromErrno(errno
);
3545 throw runtime_error("Failed to open PEM file `" + filename
+ "`: " + errMsg
);
3548 DNSKEYRecordContent drc
;
3549 shared_ptr
<DNSCryptoKeyEngine
> key
{DNSCryptoKeyEngine::makeFromPEMFile(drc
, algorithm
, *fp
, filename
)};
3551 cerr
<< "Could not convert key from PEM to internal format" << endl
;
3555 DNSSECPrivateKey dpk
;
3558 pdns::checked_stoi_into(algo
, cmds
.at(3));
3559 if (algo
== DNSSECKeeper::RSASHA1NSEC3SHA1
) {
3560 algo
= DNSSECKeeper::RSASHA1
;
3563 cerr
<< std::to_string(algo
) << endl
;
3566 if (cmds
.size() > 4) {
3567 if (pdns_iequals(cmds
.at(4), "ZSK")) {
3570 else if (pdns_iequals(cmds
.at(4), "KSK")) {
3574 cerr
<< "Unknown key flag '" << cmds
.at(4) << "'" << endl
;
3581 dpk
.setKey(key
, flags
, algo
);
3584 if (!dk
.addKey(DNSName(zone
), dpk
, id
)) {
3585 cerr
<< "Adding key failed, perhaps DNSSEC not enabled in configuration?" << endl
;
3590 cerr
<< std::to_string(id
) << "Key was added, but backend does not support returning of key id" << endl
;
3593 cerr
<< std::to_string(id
) << "Key was added, but there was a failure while returning the key id" << endl
;
3596 cout
<< std::to_string(id
) << endl
;
3599 else if (cmds
.at(0) == "import-zone-key") {
3600 if(cmds
.size() < 3) {
3601 cerr
<<"Syntax: pdnsutil import-zone-key ZONE FILE [ksk|zsk] [active|inactive]"<<endl
;
3604 string zone
= cmds
.at(1);
3605 string fname
= cmds
.at(2);
3606 DNSKEYRecordContent drc
;
3607 shared_ptr
<DNSCryptoKeyEngine
> key(DNSCryptoKeyEngine::makeFromISCFile(drc
, fname
.c_str()));
3609 uint16_t flags
= 257;
3611 bool published
=true;
3613 for(unsigned int n
= 3; n
< cmds
.size(); ++n
) {
3614 if (pdns_iequals(cmds
.at(n
), "ZSK"))
3616 else if (pdns_iequals(cmds
.at(n
), "KSK"))
3618 else if (pdns_iequals(cmds
.at(n
), "active"))
3620 else if (pdns_iequals(cmds
.at(n
), "passive") || pdns_iequals(cmds
.at(n
), "inactive")) // passive eventually needs to be removed
3622 else if (pdns_iequals(cmds
.at(n
), "published"))
3624 else if (pdns_iequals(cmds
.at(n
), "unpublished"))
3627 cerr
<< "Unknown key flag '" << cmds
.at(n
) << "'" << endl
;
3632 DNSSECPrivateKey dpk
;
3633 uint8_t algo
= key
->getAlgorithm();
3634 if (algo
== DNSSECKeeper::RSASHA1NSEC3SHA1
) {
3635 algo
= DNSSECKeeper::RSASHA1
;
3637 dpk
.setKey(key
, flags
, algo
);
3640 if (!dk
.addKey(DNSName(zone
), dpk
, id
, active
, published
)) {
3641 cerr
<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl
;
3645 cerr
<<std::to_string(id
)<<"Key was added, but backend does not support returning of key id"<<endl
;
3646 } else if (id
< -1) {
3647 cerr
<<std::to_string(id
)<<"Key was added, but there was a failure while returning the key id"<<endl
;
3649 cout
<<std::to_string(id
)<<endl
;
3652 else if (cmds
.at(0) == "export-zone-dnskey") {
3653 if(cmds
.size() < 3) {
3654 cerr
<<"Syntax: pdnsutil export-zone-dnskey ZONE KEY-ID"<<endl
;
3658 DNSName
zone(cmds
.at(1));
3659 auto id
= pdns::checked_stoi
<unsigned int>(cmds
.at(2));
3660 DNSSECPrivateKey dpk
=dk
.getKeyById(zone
, id
);
3661 cout
<< zone
<<" IN DNSKEY "<<dpk
.getDNSKEY().getZoneRepresentation() <<endl
;
3663 else if (cmds
.at(0) == "generate-zone-key") {
3664 if(cmds
.size() < 2 ) {
3665 cerr
<< "Syntax: pdnsutil generate-zone-key zsk|ksk [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
3666 #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519)
3669 #if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
3672 cerr
<< "] [bits]"<<endl
;
3675 // need to get algorithm, bits & ksk or zsk from commandline
3676 bool keyOrZone
=false;
3679 int algorithm
=DNSSECKeeper::ECDSA256
;
3680 for(unsigned int n
=1; n
< cmds
.size(); ++n
) {
3681 if (pdns_iequals(cmds
.at(n
), "zsk"))
3683 else if (pdns_iequals(cmds
.at(n
), "ksk"))
3685 else if ((tmp_algo
= DNSSECKeeper::shorthand2algorithm(cmds
.at(n
))) > 0) {
3686 algorithm
= tmp_algo
;
3688 else if (pdns::checked_stoi
<int>(cmds
.at(n
)) != 0)
3689 pdns::checked_stoi_into(bits
, cmds
.at(n
));
3691 cerr
<< "Unknown algorithm, key flag or size '" << cmds
.at(n
) << "'" << endl
;
3695 cerr
<<"Generating a " << (keyOrZone
? "KSK" : "ZSK")<<" with algorithm = "<<algorithm
<<endl
;
3697 cerr
<<"Requesting specific key size of "<<bits
<<" bits"<<endl
;
3699 shared_ptr
<DNSCryptoKeyEngine
> dpk(DNSCryptoKeyEngine::make(algorithm
));
3702 bits
= keyOrZone
? 2048 : 1024;
3704 if(algorithm
== DNSSECKeeper::ECCGOST
|| algorithm
== DNSSECKeeper::ECDSA256
|| algorithm
== DNSSECKeeper::ED25519
)
3706 else if(algorithm
== DNSSECKeeper::ECDSA384
)
3708 else if(algorithm
== DNSSECKeeper::ED448
)
3711 throw runtime_error("Can not guess key size for algorithm "+std::to_string(algorithm
));
3716 DNSSECPrivateKey dspk
;
3717 dspk
.setKey(dpk
, keyOrZone
? 257 : 256, algorithm
);
3719 // print key to stdout
3720 cout
<< "Flags: " << dspk
.getFlags() << endl
<<
3721 dspk
.getKey()->convertToISC() << endl
;
3723 else if (cmds
.at(0) == "generate-tsig-key") {
3724 string usage
= "Syntax: " + cmds
.at(0) + " name (hmac-md5|hmac-sha1|hmac-sha224|hmac-sha256|hmac-sha384|hmac-sha512)";
3725 if (cmds
.size() < 3) {
3726 cerr
<< usage
<< endl
;
3729 DNSName
name(cmds
.at(1));
3730 DNSName
algo(cmds
.at(2));
3733 key
= makeTSIGKey(algo
);
3734 } catch(const PDNSException
& e
) {
3735 cerr
<< "Could not create new TSIG key " << name
<< " " << algo
<< ": "<< e
.reason
<< endl
;
3739 UeberBackend
B("default");
3740 if (B
.setTSIGKey(name
, DNSName(algo
), key
)) { // you are feeling bored, put up DNSName(algo) up earlier
3741 cout
<< "Create new TSIG key " << name
<< " " << algo
<< " " << key
<< endl
;
3743 cerr
<< "Failure storing new TSIG key " << name
<< " " << algo
<< " " << key
<< endl
;
3748 else if (cmds
.at(0) == "import-tsig-key") {
3749 if (cmds
.size() < 4) {
3750 cerr
<< "Syntax: " << cmds
.at(0) << " name algorithm key" << endl
;
3753 DNSName
name(cmds
.at(1));
3754 string algo
= cmds
.at(2);
3755 string key
= cmds
.at(3);
3757 UeberBackend
B("default");
3758 if (B
.setTSIGKey(name
, DNSName(algo
), key
)) {
3759 cout
<< "Imported TSIG key " << name
<< " " << algo
<< endl
;
3762 cerr
<< "Failure importing TSIG key " << name
<< " " << algo
<< endl
;
3767 else if (cmds
.at(0) == "delete-tsig-key") {
3768 if (cmds
.size() < 2) {
3769 cerr
<< "Syntax: " << cmds
.at(0) << " name" << endl
;
3772 DNSName
name(cmds
.at(1));
3774 UeberBackend
B("default");
3775 if (B
.deleteTSIGKey(name
)) {
3776 cout
<< "Deleted TSIG key " << name
<< endl
;
3779 cerr
<< "Failure deleting TSIG key " << name
<< endl
;
3784 else if (cmds
.at(0) == "list-tsig-keys") {
3785 std::vector
<struct TSIGKey
> keys
;
3786 UeberBackend
B("default");
3787 if (B
.getTSIGKeys(keys
)) {
3788 for (const TSIGKey
& key
: keys
) {
3789 cout
<< key
.name
.toString() << " " << key
.algorithm
.toString() << " " << key
.key
<< endl
;
3794 else if (cmds
.at(0) == "activate-tsig-key") {
3796 if (cmds
.size() < 4) {
3797 cerr
<< "Syntax: " << cmds
.at(0) << " ZONE NAME {primary|secondary}" << endl
;
3800 DNSName
zname(cmds
.at(1));
3801 string name
= cmds
.at(2);
3802 if (cmds
.at(3) == "primary" || cmds
.at(3) == "producer")
3803 metaKey
= "TSIG-ALLOW-AXFR";
3804 else if (cmds
.at(3) == "secondary" || cmds
.at(3) == "consumer")
3805 metaKey
= "AXFR-MASTER-TSIG";
3807 cerr
<< "Invalid parameter '" << cmds
.at(3) << "', expected primary or secondary type" << endl
;
3810 UeberBackend
B("default");
3812 if (!B
.getDomainInfo(zname
, di
)) {
3813 cerr
<< "Zone '" << zname
<< "' does not exist" << endl
;
3816 std::vector
<std::string
> meta
;
3817 if (!B
.getDomainMetadata(zname
, metaKey
, meta
)) {
3818 cerr
<< "Failure enabling TSIG key " << name
<< " for " << zname
<< endl
;
3822 for (const std::string
& tmpname
: meta
) {
3823 if (tmpname
== name
) {
3829 meta
.push_back(name
);
3830 if (B
.setDomainMetadata(zname
, metaKey
, meta
)) {
3831 cout
<< "Enabled TSIG key " << name
<< " for " << zname
<< endl
;
3834 cerr
<< "Failure enabling TSIG key " << name
<< " for " << zname
<< endl
;
3839 else if (cmds
.at(0) == "deactivate-tsig-key") {
3841 if (cmds
.size() < 4) {
3842 cerr
<< "Syntax: " << cmds
.at(0) << " ZONE NAME {primary|secondary|producer|consumer}" << endl
;
3845 DNSName
zname(cmds
.at(1));
3846 string name
= cmds
.at(2);
3847 if (cmds
.at(3) == "primary" || cmds
.at(3) == "producer")
3848 metaKey
= "TSIG-ALLOW-AXFR";
3849 else if (cmds
.at(3) == "secondary" || cmds
.at(3) == "consumer")
3850 metaKey
= "AXFR-MASTER-TSIG";
3852 cerr
<< "Invalid parameter '" << cmds
.at(3) << "', expected primary or secondary type" << endl
;
3856 UeberBackend
B("default");
3858 if (!B
.getDomainInfo(zname
, di
)) {
3859 cerr
<< "Zone '" << zname
<< "' does not exist" << endl
;
3862 std::vector
<std::string
> meta
;
3863 if (!B
.getDomainMetadata(zname
, metaKey
, meta
)) {
3864 cerr
<< "Failure disabling TSIG key " << name
<< " for " << zname
<< endl
;
3867 std::vector
<std::string
>::iterator iter
= meta
.begin();
3868 for (; iter
!= meta
.end(); ++iter
)
3871 if (iter
!= meta
.end())
3873 if (B
.setDomainMetadata(zname
, metaKey
, meta
)) {
3874 cout
<< "Disabled TSIG key " << name
<< " for " << zname
<< endl
;
3877 cerr
<< "Failure disabling TSIG key " << name
<< " for " << zname
<< endl
;
3882 else if (cmds
.at(0) == "get-meta") {
3883 UeberBackend
B("default");
3884 if (cmds
.size() < 2) {
3885 cerr
<< "Syntax: " << cmds
.at(0) << " zone [kind kind ..]" << endl
;
3888 DNSName
zone(cmds
.at(1));
3889 vector
<string
> keys
;
3892 if (!B
.getDomainInfo(zone
, di
)) {
3893 cerr
<< "Invalid zone '" << zone
<< "'" << endl
;
3897 if (cmds
.size() > 2) {
3898 keys
.assign(cmds
.begin() + 2, cmds
.end());
3899 std::cout
<< "Metadata for '" << zone
<< "'" << endl
;
3900 for(const auto& kind
: keys
) {
3901 vector
<string
> meta
;
3903 if (B
.getDomainMetadata(zone
, kind
, meta
)) {
3904 cout
<< kind
<< " = " << boost::join(meta
, ", ") << endl
;
3908 std::map
<std::string
, std::vector
<std::string
> > meta
;
3909 std::cout
<< "Metadata for '" << zone
<< "'" << endl
;
3910 B
.getAllDomainMetadata(zone
, meta
);
3911 for(const auto& each_meta
: meta
) {
3912 cout
<< each_meta
.first
<< " = " << boost::join(each_meta
.second
, ", ") << endl
;
3917 else if (cmds
.at(0) == "set-meta" || cmds
.at(0) == "add-meta") {
3918 if (cmds
.size() < 3) {
3919 cerr
<< "Syntax: " << cmds
.at(0) << " ZONE KIND [VALUE VALUE ..]" << endl
;
3922 DNSName
zone(cmds
.at(1));
3923 string kind
= cmds
.at(2);
3924 const static std::array
<string
, 7> multiMetaWhitelist
= {"ALLOW-AXFR-FROM", "ALLOW-DNSUPDATE-FROM",
3925 "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE", "GSS-ALLOW-AXFR-PRINCIPAL",
3927 bool clobber
= true;
3928 if (cmds
.at(0) == "add-meta") {
3930 if (find(multiMetaWhitelist
.begin(), multiMetaWhitelist
.end(), kind
) == multiMetaWhitelist
.end() && kind
.find("X-") != 0) {
3931 cerr
<<"Refusing to add metadata to single-value metadata "<<kind
<<endl
;
3935 vector
<string
> meta(cmds
.begin() + 3, cmds
.end());
3936 return addOrSetMeta(zone
, kind
, meta
, clobber
);
3938 else if (cmds
.at(0) == "hsm") {
3940 UeberBackend
B("default");
3941 if (cmds
.size() < 2) {
3942 cerr
<< "Missing sub-command for pdnsutil hsm"<< std::endl
;
3945 else if (cmds
.at(1) == "assign") {
3946 DNSCryptoKeyEngine::storvector_t storvect
;
3948 std::vector
<DNSBackend::KeyData
> keys
;
3950 if (cmds
.size() < 9) {
3951 std::cout
<< "Usage: pdnsutil hsm assign ZONE ALGORITHM {ksk|zsk} MODULE TOKEN PIN LABEL (PUBLABEL)" << std::endl
;
3955 DNSName
zone(cmds
.at(2));
3958 if (!B
.getDomainInfo(zone
, di
)) {
3959 cerr
<< "Unable to assign module to unknown zone '" << zone
<< "'" << std::endl
;
3963 int algorithm
= DNSSECKeeper::shorthand2algorithm(cmds
.at(3));
3965 cerr
<< "Unable to use unknown algorithm '" << cmds
.at(3) << "'" << std::endl
;
3969 bool keyOrZone
= (cmds
.at(4) == "ksk" ? true : false);
3970 string module
= cmds
.at(5);
3971 string slot
= cmds
.at(6);
3972 string pin
= cmds
.at(7);
3973 string label
= cmds
.at(8);
3975 if (cmds
.size() > 9)
3976 pub_label
= cmds
.at(9);
3980 std::ostringstream iscString
;
3981 iscString
<< "Private-key-format: v1.2" << std::endl
<<
3982 "Algorithm: " << algorithm
<< std::endl
<<
3983 "Engine: " << module
<< std::endl
<<
3984 "Slot: " << slot
<< std::endl
<<
3985 "PIN: " << pin
<< std::endl
<<
3986 "Label: " << label
<< std::endl
<<
3987 "PubLabel: " << pub_label
<< std::endl
;
3989 DNSKEYRecordContent drc
;
3991 shared_ptr
<DNSCryptoKeyEngine
> dke(DNSCryptoKeyEngine::makeFromISCString(drc
, iscString
.str()));
3992 if(!dke
->checkKey()) {
3993 cerr
<< "Invalid DNS Private Key in engine " << module
<< " slot " << slot
<< std::endl
;
3996 DNSSECPrivateKey dpk
;
3997 dpk
.setKey(dke
, keyOrZone
? 257 : 256);
3999 // make sure this key isn't being reused.
4000 B
.getDomainKeys(zone
, keys
);
4003 for(DNSBackend::KeyData
& kd
: keys
) {
4004 if (kd
.content
== iscString
.str()) {
4005 // it's this one, I guess...
4012 cerr
<< "You have already assigned this key with ID=" << id
<< std::endl
;
4016 if (!dk
.addKey(zone
, dpk
, id
)) {
4017 cerr
<< "Unable to assign module slot to zone" << std::endl
;
4021 cerr
<< "Module " << module
<< " slot " << slot
<< " assigned to " << zone
<< " with key id " << id
<< endl
;
4025 else if (cmds
.at(1) == "create-key") {
4027 if (cmds
.size() < 4) {
4028 cerr
<< "Usage: pdnsutil hsm create-key ZONE KEY-ID [BITS]" << endl
;
4032 DNSName
zone(cmds
.at(2));
4036 if (!B
.getDomainInfo(zone
, di
)) {
4037 cerr
<< "Unable to create key for unknown zone '" << zone
<< "'" << std::endl
;
4041 pdns::checked_stoi_into(id
, cmds
.at(3));
4042 std::vector
<DNSBackend::KeyData
> keys
;
4043 if (!B
.getDomainKeys(zone
, keys
)) {
4044 cerr
<< "No keys found for zone " << zone
<< std::endl
;
4048 std::unique_ptr
<DNSCryptoKeyEngine
> dke
= nullptr;
4049 // lookup correct key
4050 for(DNSBackend::KeyData
&kd
: keys
) {
4053 DNSKEYRecordContent dkrc
;
4054 dke
= DNSCryptoKeyEngine::makeFromISCString(dkrc
, kd
.content
);
4059 cerr
<< "Could not find key with ID " << id
<< endl
;
4062 if (cmds
.size() > 4) {
4063 pdns::checked_stoi_into(bits
, cmds
.at(4));
4066 cerr
<< "Invalid bit size " << bits
<< "given, must be positive integer";
4071 } catch (PDNSException
& e
) {
4072 cerr
<< e
.reason
<< endl
;
4076 cerr
<< "Key of size " << dke
->getBits() << " created" << std::endl
;
4080 cerr
<<"PKCS#11 support not enabled"<<endl
;
4084 else if (cmds
.at(0) == "b2b-migrate") {
4085 if (cmds
.size() < 3) {
4086 cerr
<< "Usage: b2b-migrate OLD NEW" << endl
;
4090 if (cmds
.at(1) == cmds
.at(2)) {
4091 cerr
<< "Error: b2b-migrate OLD NEW: OLD cannot be the same as NEW" << endl
;
4095 unique_ptr
<DNSBackend
> src
{nullptr};
4096 unique_ptr
<DNSBackend
> tgt
{nullptr};
4098 for (auto& backend
: BackendMakers().all()) {
4099 if (backend
->getPrefix() == cmds
.at(1)) {
4100 src
= std::move(backend
);
4102 else if (backend
->getPrefix() == cmds
.at(2)) {
4103 tgt
= std::move(backend
);
4107 if (src
== nullptr) {
4108 cerr
<< "Unknown source backend '" << cmds
.at(1) << "'" << endl
;
4111 if (tgt
== nullptr) {
4112 cerr
<< "Unknown target backend '" << cmds
.at(2) << "'" << endl
;
4116 cout
<<"Moving zone(s) from "<<src
->getPrefix()<<" to "<<tgt
->getPrefix()<<endl
;
4118 vector
<DomainInfo
> domains
;
4120 tgt
->getAllDomains(&domains
, false, true);
4121 if (!domains
.empty())
4122 throw PDNSException("Target backend has zone(s), please clean it first");
4124 src
->getAllDomains(&domains
, false, true);
4126 for(const DomainInfo
& di
: domains
) {
4129 DNSResourceRecord rr
;
4130 cout
<<"Processing '"<<di
.zone
<<"'"<<endl
;
4132 if (!tgt
->createDomain(di
.zone
, di
.kind
, di
.primaries
, di
.account
))
4133 throw PDNSException("Failed to create zone");
4134 if (!tgt
->getDomainInfo(di
.zone
, di_new
)) throw PDNSException("Failed to create zone");
4136 if (!src
->list(di
.zone
, di
.id
, true)) throw PDNSException("Failed to list records");
4139 tgt
->startTransaction(di
.zone
, di_new
.id
);
4141 while(src
->get(rr
)) {
4142 rr
.domain_id
= di_new
.id
;
4143 if (!tgt
->feedRecord(rr
, DNSName())) throw PDNSException("Failed to feed record");
4149 if (src
->listComments(di
.id
)) {
4151 while(src
->getComment(c
)) {
4152 c
.domain_id
= di_new
.id
;
4153 if (!tgt
->feedComment(c
)) {
4154 throw PDNSException("Target backend does not support comments - remove them first");
4161 std::map
<std::string
, std::vector
<std::string
> > meta
;
4162 if (src
->getAllDomainMetadata(di
.zone
, meta
)) {
4163 for (const auto& i
: meta
) {
4164 if (!tgt
->setDomainMetadata(di
.zone
, i
.first
, i
.second
))
4165 throw PDNSException("Failed to feed zone metadata");
4171 // temp var for KeyID
4173 std::vector
<DNSBackend::KeyData
> keys
;
4174 if (src
->getDomainKeys(di
.zone
, keys
)) {
4175 for(const DNSBackend::KeyData
& k
: keys
) {
4176 tgt
->addDomainKey(di
.zone
, k
, keyID
);
4180 tgt
->commitTransaction();
4181 cout
<<"Moved "<<nr
<<" record(s), "<<nc
<<" comment(s), "<<nm
<<" metadata(s) and "<<nk
<<" cryptokey(s)"<<endl
;
4186 std::vector
<struct TSIGKey
> tkeys
;
4187 if (src
->getTSIGKeys(tkeys
)) {
4188 for(auto& tk
: tkeys
) {
4189 if (!tgt
->setTSIGKey(tk
.name
, tk
.algorithm
, tk
.key
)) throw PDNSException("Failed to feed TSIG key");
4193 cout
<<"Moved "<<ntk
<<" TSIG key(s)"<<endl
;
4195 cout
<<"Remember to drop the old backend and run rectify-all-zones"<<endl
;
4199 else if (cmds
.at(0) == "backend-cmd") {
4200 if (cmds
.size() < 3) {
4201 cerr
<<"Usage: backend-cmd BACKEND CMD [CMD..]"<<endl
;
4205 std::unique_ptr
<DNSBackend
> matchingBackend
{nullptr};
4207 for (auto& backend
: BackendMakers().all()) {
4208 if (backend
->getPrefix() == cmds
.at(1)) {
4209 matchingBackend
= std::move(backend
);
4213 if (matchingBackend
== nullptr) {
4214 cerr
<< "Unknown backend '" << cmds
.at(1) << "'" << endl
;
4218 for (auto i
= next(begin(cmds
), 2); i
!= end(cmds
); ++i
) {
4219 cerr
<< "== " << *i
<< endl
;
4220 cout
<< matchingBackend
->directBackendCmd(*i
);
4226 cerr
<< "Unknown command '" << cmds
.at(0) << "'" << endl
;
4231 catch(PDNSException
& ae
) {
4232 cerr
<<"Error: "<<ae
.reason
<<endl
;
4235 catch(std::exception
& e
) {
4236 cerr
<<"Error: "<<e
.what()<<endl
;
4241 cerr
<<"Caught an unknown exception"<<endl
;