]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/pdnsutil.cc
Merge pull request #13387 from omoerbeek/rec-b-root-servers
[thirdparty/pdns.git] / pdns / pdnsutil.cc
1
2 #include <boost/smart_ptr/make_shared_array.hpp>
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6
7 #include <fcntl.h>
8
9 #include "credentials.hh"
10 #include "dnsseckeeper.hh"
11 #include "dnssecinfra.hh"
12 #include "statbag.hh"
13 #include "base32.hh"
14 #include "base64.hh"
15
16 #include <boost/program_options.hpp>
17 #include <boost/assign/std/vector.hpp>
18 #include <boost/assign/list_of.hpp>
19 #include "json11.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"
31 #include "misc.hh"
32 #include "zonemd.hh"
33 #include <fstream>
34 #include <utility>
35 #include <cerrno>
36 #include <sys/stat.h>
37 #include <termios.h> //termios, TCSANOW, ECHO, ICANON
38 #include "opensslsigners.hh"
39 #ifdef HAVE_LIBSODIUM
40 #include <sodium.h>
41 #endif
42 #ifdef HAVE_SQLITE3
43 #include "ssqlite3.hh"
44 #include "bind-dnssec.schema.sqlite3.sql.h"
45 #endif
46
47 StatBag S;
48 AuthPacketCache PC;
49 AuthQueryCache QC;
50 AuthZoneCache g_zoneCache;
51 uint16_t g_maxNSEC3Iterations{0};
52
53 namespace po = boost::program_options;
54 po::variables_map g_vm;
55
56 string g_programname="pdns";
57
58 namespace {
59 bool g_verbose;
60 }
61
62 ArgvMap &arg()
63 {
64 static ArgvMap arg;
65 return arg;
66 }
67
68 static std::string comboAddressVecToString(const std::vector<ComboAddress>& vec) {
69 vector<string> strs;
70 strs.reserve(vec.size());
71 for (const auto& ca : vec) {
72 strs.push_back(ca.toStringWithPortExcept(53));
73 }
74 return boost::join(strs, ",");
75 }
76
77 static void loadMainConfig(const std::string& configdir)
78 {
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);
87
88 if(::arg().mustDo("help")) {
89 cout<<"syntax:"<<endl<<endl;
90 cout<<::arg().helpstring(::arg()["help"])<<endl;
91 exit(0);
92 }
93
94 if(!::arg()["config-name"].empty())
95 g_programname+="-"+::arg()["config-name"];
96
97 string configname=::arg()["config-dir"]+"/"+g_programname+".conf";
98 cleanSlashes(configname);
99
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());
120
121 if(!::arg()["load-modules"].empty()) {
122 vector<string> modules;
123
124 stringtok(modules,::arg()["load-modules"], ", ");
125 if (!UeberBackend::loadModules(modules, ::arg()["module-dir"])) {
126 exit(1);
127 }
128 }
129
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"));
134
135 //cerr<<"Backend: "<<::arg()["launch"]<<", '" << ::arg()["gmysql-dbname"] <<"'" <<endl;
136
137 S.declare("qsize-q","Number of questions waiting for database attention");
138
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";
149
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;
153
154 #ifdef HAVE_LIBSODIUM
155 if (sodium_init() == -1) {
156 cerr<<"Unable to initialize sodium crypto library"<<endl;
157 exit(99);
158 }
159 #endif
160 openssl_seed();
161
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;
165 exit(1);
166 }
167 }
168
169 UeberBackend::go();
170 }
171
172 static bool rectifyZone(DNSSECKeeper& dk, const DNSName& zone, bool quiet = false, bool rectifyTransaction = true)
173 {
174 string output;
175 string error;
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()) {
180 cerr<<output<<endl;
181 }
182 if (!ret && !error.empty()) {
183 cerr<<error<<endl;
184 }
185 }
186 return ret;
187 }
188
189 static void dbBench(const std::string& fname)
190 {
191 ::arg().set("query-cache-ttl")="0";
192 ::arg().set("negquery-cache-ttl")="0";
193 UeberBackend B("default");
194
195 vector<string> domains;
196 if(!fname.empty()) {
197 ifstream ifs(fname.c_str());
198 if(!ifs) {
199 cerr << "Could not open '" << fname << "' for reading zone names to query" << endl;
200 }
201 string line;
202 while(getline(ifs,line)) {
203 boost::trim(line);
204 domains.push_back(line);
205 }
206 }
207 if(domains.empty())
208 domains.push_back("powerdns.com");
209
210 int n=0;
211 DNSZoneRecord rr;
212 DTime dt;
213 dt.set();
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);
218 while(B.get(rr)) {
219 hits++;
220 }
221 B.lookup(QType(QType::A), DNSName(std::to_string(dns_random_uint32()))+domain, -1);
222 while(B.get(rr)) {
223 }
224 misses++;
225
226 }
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;
230 }
231
232 static bool rectifyAllZones(DNSSECKeeper &dk, bool quiet = false)
233 {
234 UeberBackend B("default");
235 vector<DomainInfo> domainInfo;
236 bool result = true;
237
238 B.getAllDomains(&domainInfo, false, false);
239 for(const DomainInfo& di : domainInfo) {
240 if (!quiet) {
241 cerr<<"Rectifying "<<di.zone<<": ";
242 }
243 if (!rectifyZone(dk, di.zone, quiet)) {
244 result = false;
245 }
246 }
247 if (!quiet) {
248 cout<<"Rectified "<<domainInfo.size()<<" zones."<<endl;
249 }
250 return result;
251 }
252
253 static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, const vector<DNSResourceRecord>* suppliedrecords=nullptr)
254 {
255 uint64_t numerrors=0, numwarnings=0;
256
257 DomainInfo di;
258 try {
259 if (!B.getDomainInfo(zone, di, false)) {
260 cout << "[Error] Unable to get zone information for zone '" << zone << "'" << endl;
261 return 1;
262 }
263 } catch(const PDNSException &e) {
264 if (di.kind == DomainInfo::Secondary) {
265 cout << "[Error] non-IP address for primaries: " << e.reason << endl;
266 numerrors++;
267 }
268 }
269
270 SOAData sd;
271 try {
272 if (!B.getSOAUncached(zone, sd)) {
273 cout << "[Error] No SOA record present, or active, in zone '" << zone << "'" << endl;
274 numerrors++;
275 cout << "Checked 0 records of '" << zone << "', " << numerrors << " errors, 0 warnings." << endl;
276 return 1;
277 }
278 }
279 catch (const PDNSException& e) {
280 cout << "[Error] SOA lookup failed for zone '" << zone << "': " << e.reason << endl;
281 numerrors++;
282 if (sd.db == nullptr) {
283 return 1;
284 }
285 }
286 catch (const std::exception& e) {
287 cout << "[Error] SOA lookup failed for zone '" << zone << "': " << e.what() << endl;
288 numerrors++;
289 if (sd.db == nullptr) {
290 return 1;
291 }
292 }
293
294 NSEC3PARAMRecordContent ns3pr;
295 bool narrow = false;
296 bool haveNSEC3 = dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
297 bool isOptOut=(haveNSEC3 && ns3pr.d_flags);
298
299 bool isSecure=dk.isSecuredZone(zone);
300 bool presigned=dk.isPresigned(zone);
301 vector<string> checkKeyErrors;
302 bool validKeys=dk.checkKeys(zone, checkKeyErrors);
303
304 if (haveNSEC3) {
305 if(isSecure && zone.wirelength() > 222) {
306 numerrors++;
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;
308 }
309
310 vector<DNSBackend::KeyData> dbkeyset;
311 B.getDomainKeys(zone, dbkeyset);
312
313 for (DNSBackend::KeyData& kd : dbkeyset) {
314 DNSKEYRecordContent dkrc;
315 DNSCryptoKeyEngine::makeFromISCString(dkrc, kd.content);
316
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;
319 numerrors++;
320 }
321 }
322 }
323
324 if (!validKeys) {
325 numerrors++;
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;
329 }
330 }
331
332 // Check for delegation in parent zone
333 DNSName parent(zone);
334 while(parent.chopOff()) {
335 SOAData sd_p;
336 if(B.getSOAUncached(parent, sd_p)) {
337 bool ns=false;
338 DNSZoneRecord zr;
339 B.lookup(QType(QType::ANY), zone, sd_p.domain_id);
340 while(B.get(zr))
341 ns |= (zr.dr.d_type == QType::NS);
342 if (!ns) {
343 cout<<"[Error] No delegation for zone '"<<zone<<"' in parent '"<<parent<<"'"<<endl;
344 numerrors++;
345 }
346 break;
347 }
348 }
349
350
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;
359
360 ostringstream content;
361 pair<map<string, unsigned int>::iterator,bool> ret;
362
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);
369 }
370 }
371 else
372 records=*suppliedrecords;
373
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);
379 }
380 if(rr.qtype.getCode() == QType::A) {
381 arecords.insert(rr.qname);
382 }
383 if(rr.qtype.getCode() == QType::AAAA) {
384 aaaarecords.insert(rr.qname);
385 }
386 if(rr.qtype.getCode() == QType::SOA) {
387 vector<string>parts;
388 stringtok(parts, rr.content);
389
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;
392 }
393
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;
397 numwarnings++;
398 }
399 }
400
401 ostringstream o;
402 o<<rr.content;
403 for(int pleft=parts.size(); pleft < 7; ++pleft) {
404 o<<" 0";
405 }
406 rr.content=o.str();
407 }
408
409 if(rr.qtype.getCode() == QType::TXT && !rr.content.empty() && rr.content[0]!='"')
410 rr.content = "\""+rr.content+"\"";
411
412 try {
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);
420 }
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;
423 numwarnings++;
424 }
425 }
426 } else {
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;
430 numwarnings++;
431 }
432 }
433 }
434 catch(std::exception& e)
435 {
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;
438 numerrors++;
439 continue;
440 }
441
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;
444 numerrors++;
445 continue;
446 }
447
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;
454 numwarnings++;
455 }
456
457 if(svcbrc->getPriority() != 0) {
458 // Service Form
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
463 * (Section 2.4.3).
464 */
465 cout<<"[Warning] "<<rr.qname<<"|"<<rr.qtype.toString()<<" is not self-consistent: 'no-default-alpn' parameter without 'alpn' parameter"<<endl;
466 numwarnings++;
467 }
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;
473 numwarnings++;
474 }
475 }
476 }
477 }
478
479 switch (rr.qtype.getCode()) {
480 case QType::SVCB:
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;
484 numwarnings++;
485 }
486 svcbAliases.insert(rr.qname);
487 }
488 svcbTargets.emplace(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint));
489 svcbRecords.insert(rr.qname);
490 break;
491 case QType::HTTPS:
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;
495 numwarnings++;
496 }
497 httpsAliases.insert(rr.qname);
498 }
499 httpsTargets.emplace(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint));
500 httpsRecords.insert(rr.qname);
501 break;
502 }
503 }
504
505 content.str("");
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);
510 }
511 if (recordcontents.count(contentstr)) {
512 cout<<"[Error] Duplicate record found in rrset: '"<<rr.qname<<" IN "<<rr.qtype.toString()<<" "<<rr.content<<"'"<<endl;
513 numerrors++;
514 continue;
515 } else
516 recordcontents.insert(contentstr);
517
518 content.str("");
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)<<")";
523 }
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;
527 numerrors++;
528 continue;
529 }
530
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;
534 numwarnings++;
535 }
536
537 if(rr.qname==zone) {
538 // apex checks
539 if (rr.qtype.getCode() == QType::NS) {
540 hasNsAtApex=true;
541 } else if (rr.qtype.getCode() == QType::DS) {
542 cout<<"[Warning] DS at apex in zone '"<<zone<<"', should not be here."<<endl;
543 numwarnings++;
544 }
545 } else {
546 // non-apex checks
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;
549 numerrors++;
550 continue;
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;
553 numwarnings++;
554 } else if (rr.qtype.getCode() == QType::NS) {
555 if (DNSName(rr.content).isPartOf(rr.qname)) {
556 checkglue.insert(DNSName(toLower(rr.content)));
557 }
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);
561 }
562 }
563
564 // DNAMEs can occur both at the apex and below it
565 if (rr.qtype == QType::DNAME) {
566 checkOcclusion.insert({rr.qname, rr.qtype});
567 }
568
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;
571
572 // Check if the DNSNames that should be hostnames, are hostnames
573 try {
574 checkHostnameCorrectness(rr);
575 } catch (const std::exception& e) {
576 cout << "[Warning] " << rr.qtype.toString() << " record in zone '" << zone << ": " << e.what() << endl;
577 numwarnings++;
578 }
579
580 if (rr.qtype.getCode() == QType::CNAME) {
581 if (!cnames.count(rr.qname))
582 cnames.insert(rr.qname);
583 else {
584 cout<<"[Error] Duplicate CNAME found at '"<<rr.qname<<"'"<<endl;
585 numerrors++;
586 continue;
587 }
588 } else {
589 if (rr.qtype.getCode() == QType::RRSIG) {
590 if(!presigned) {
591 cout<<"[Error] RRSIG found at '"<<rr.qname<<"' in non-presigned zone. These do not belong in the database."<<endl;
592 numerrors++;
593 continue;
594 }
595 } else
596 noncnames.insert(rr.qname);
597 }
598
599 if (rr.qtype == QType::MX || rr.qtype == QType::NS || rr.qtype == QType::SRV) {
600 checkCNAME.push_back(rr);
601 }
602
603 if(rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3)
604 {
605 cout<<"[Error] NSEC or NSEC3 found at '"<<rr.qname<<"'. These do not belong in the database."<<endl;
606 numerrors++;
607 continue;
608 }
609
610 if(!presigned && rr.qtype.getCode() == QType::DNSKEY)
611 {
612 if(::arg().mustDo("direct-dnskey"))
613 {
614 if(rr.ttl != sd.minimum)
615 {
616 cout<<"[Warning] DNSKEY TTL of "<<rr.ttl<<" at '"<<rr.qname<<"' differs from SOA minimum of "<<sd.minimum<<endl;
617 numwarnings++;
618 }
619 }
620 else
621 {
622 cout<<"[Warning] DNSKEY at '"<<rr.qname<<"' in non-presigned zone will mostly be ignored and can cause problems."<<endl;
623 numwarnings++;
624 }
625 }
626 }
627
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;
631 numerrors++;
632 }
633 }
634
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.
641 cout<<"[Warning] ";
642 DNSName wcname(name);
643 wcname.chopOff();
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<<"'.";
647 } else {
648 cout<<"No record for '"<<name<<"' exists, but a TLSA record for '"<<i<<"' does.";
649 }
650 numwarnings++;
651 cout<<" A query for '"<<name<<"' will yield an empty response. This is most likely a mistake, please create records for '"<<name<<"'."<<endl;
652 }
653 }
654
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);
661
662 if (name == target) {
663 cout<<"[Error] SVCB record "<<name<<" has itself as target."<<endl;
664 numerrors++;
665 }
666
667 if (prio == 0) {
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;
671 numwarnings++;
672 }
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;
675 numerrors++;
676 }
677 }
678 }
679
680 auto trueTarget = target.isRoot() ? name : target;
681 if (prio > 0) {
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;
684 numwarnings++;
685 }
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;
688 numwarnings++;
689 }
690 }
691 }
692
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);
699
700 if (name == target) {
701 cout<<"[Error] HTTPS record "<<name<<" has itself as target."<<endl;
702 numerrors++;
703 }
704
705 if (prio == 0) {
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;
709 numwarnings++;
710 }
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;
713 numerrors++;
714 }
715 }
716 }
717
718 auto trueTarget = target.isRoot() ? name : target;
719 if (prio > 0) {
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;
722 numwarnings++;
723 }
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;
726 numwarnings++;
727 }
728 }
729 }
730
731 if(!hasNsAtApex) {
732 cout<<"[Error] No NS record at zone apex in zone '"<<zone<<"'"<<endl;
733 numerrors++;
734 }
735
736 for(const auto &qname : checkglue) {
737 if (!glue.count(qname)) {
738 cout<<"[Warning] Missing glue for '"<<qname<<"' in zone '"<<zone<<"'"<<endl;
739 numwarnings++;
740 }
741 }
742
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 ) ) ) {
749 continue;
750 }
751
752 // for most types, X occludes X and (type-dependent) almost everything under X
753 if( rr.qname.isPartOf( qname.first ) ) {
754
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 ) {
757 continue;
758 }
759
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";
767 } else {
768 cout << "DNAME";
769 }
770 cout << " at '" << qname.first << "'" << endl;
771 numwarnings++;
772 }
773 }
774 }
775 }
776
777 for (auto const &rr : checkCNAME) {
778 DNSName target;
779 shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
780 switch (rr.qtype) {
781 case QType::MX:
782 target = std::dynamic_pointer_cast<MXRecordContent>(drc)->d_mxname;
783 break;
784 case QType::SRV:
785 target = std::dynamic_pointer_cast<SRVRecordContent>(drc)->d_target;
786 break;
787 case QType::NS:
788 target = std::dynamic_pointer_cast<NSRecordContent>(drc)->getNS();
789 break;
790 default:
791 // programmer error, but let's not abort() :)
792 break;
793 }
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;
796 numwarnings++;
797 }
798 }
799
800 bool ok, ds_ns, done;
801 for( const auto &rr : records ) {
802 ok = ( rr.auth == 1 );
803 ds_ns = false;
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 ) {
808 ds_ns = true;
809 }
810 if ( done ) {
811 continue;
812 }
813 if( rr.auth == 0 ) {
814 if( rr.qname.isPartOf( qname.first ) && ( qname.first != rr.qname || rr.qtype != QType::DS ) ) {
815 ok = done = true;
816 }
817 if( rr.qtype == QType::ENT && qname.first.isPartOf( rr.qname ) ) {
818 ok = done = true;
819 }
820 } else if( rr.qname.isPartOf( qname.first ) && ( ( qname.first != rr.qname || rr.qtype != QType::DS ) || rr.qtype == QType::NS ) ) {
821 ok = false;
822 done = true;
823 }
824 }
825 }
826 if( ! ds_ns && rr.qtype.getCode() == QType::DS && rr.qname != zone ) {
827 cout << "[Warning] DS record without a delegation '" << rr.qname<<"'." << endl;
828 numwarnings++;
829 }
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;
832 numerrors++;
833 }
834 }
835
836 std::map<std::string, std::vector<std::string>> metadatas;
837 if (B.getAllDomainMetadata(zone, metadatas)) {
838 for (const auto& metaData : metadatas) {
839 set<string> seen;
840 set<string> messaged;
841
842 for (const auto& value : metaData.second) {
843 if (seen.count(value) == 0) {
844 seen.insert(value);
845 }
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;
848 numerrors++;
849 messaged.insert(value);
850 }
851 }
852 }
853 }
854
855 cout<<"Checked "<<records.size()<<" records of '"<<zone<<"', "<<numerrors<<" errors, "<<numwarnings<<" warnings."<<endl;
856 if(!numerrors)
857 return EXIT_SUCCESS;
858 return EXIT_FAILURE;
859 }
860
861 static int checkAllZones(DNSSECKeeper &dk, bool exitOnError)
862 {
863 UeberBackend B("default");
864 vector<DomainInfo> domainInfo;
865 multi_index_container<
866 DomainInfo,
867 indexed_by<
868 ordered_non_unique< member<DomainInfo,DNSName,&DomainInfo::zone>, CanonDNSNameCompare >,
869 ordered_non_unique< member<DomainInfo,uint32_t,&DomainInfo::id> >
870 >
871 > seenInfos;
872 auto& seenNames = seenInfos.get<0>();
873 auto& seenIds = seenInfos.get<1>();
874
875 B.getAllDomains(&domainInfo, true, true);
876 int errors=0;
877 for (auto& di : domainInfo) {
878 if (checkZone(dk, B, di.zone) > 0) {
879 errors++;
880 }
881
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;
885 errors++;
886 }
887
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;
891 errors++;
892 }
893
894 seenInfos.insert(std::move(di));
895
896 if (errors && exitOnError) {
897 return EXIT_FAILURE;
898 }
899 }
900 cout<<"Checked "<<domainInfo.size()<<" zones, "<<errors<<" had errors."<<endl;
901 if(!errors)
902 return EXIT_SUCCESS;
903 return EXIT_FAILURE;
904 }
905
906 static int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
907 {
908 UeberBackend B("default");
909 SOAData sd;
910 if(!B.getSOAUncached(zone, sd)) {
911 cerr<<"No SOA for zone '"<<zone<<"'"<<endl;
912 return -1;
913 }
914
915 if (dk.isPresigned(zone)) {
916 cerr<<"Serial increase of presigned zone '"<<zone<<"' is not allowed."<<endl;
917 return -1;
918 }
919
920 string soaEditKind;
921 dk.getSoaEdit(zone, soaEditKind);
922
923 DNSResourceRecord rr;
924 makeIncreasedSOARecord(sd, "SOA-EDIT-INCREASE", soaEditKind, rr);
925
926 sd.db->startTransaction(zone, -1);
927
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;
931 return -1;
932 }
933
934 if (sd.db->doesDNSSEC()) {
935 NSEC3PARAMRecordContent ns3pr;
936 bool narrow = false;
937 bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
938
939 DNSName ordername;
940 if(haveNSEC3) {
941 if(!narrow)
942 ordername=DNSName(toBase32Hex(hashQNameWithSalt(ns3pr, zone)));
943 } else
944 ordername=DNSName("");
945 if(g_verbose)
946 cerr<<"'"<<rr.qname<<"' -> '"<< ordername <<"'"<<endl;
947 sd.db->updateDNSSECOrderNameAndAuth(sd.domain_id, rr.qname, ordername, true);
948 }
949
950 sd.db->commitTransaction();
951
952 cout<<"SOA serial for zone "<<zone<<" set to "<<sd.serial<<endl;
953 return 0;
954 }
955
956 static int deleteZone(const DNSName &zone) {
957 UeberBackend B;
958 DomainInfo di;
959 if (! B.getDomainInfo(zone, di)) {
960 cerr << "Zone '" << zone << "' not found!" << endl;
961 return EXIT_FAILURE;
962 }
963
964 di.backend->startTransaction(zone, -1);
965 try {
966 if(di.backend->deleteDomain(zone)) {
967 di.backend->commitTransaction();
968 return EXIT_SUCCESS;
969 }
970 } catch (...) {
971 di.backend->abortTransaction();
972 throw;
973 }
974
975 di.backend->abortTransaction();
976
977 cerr << "Failed to delete zone '" << zone << "'" << endl;
978 ;
979 return EXIT_FAILURE;
980 }
981
982 static void listKey(DomainInfo const &di, DNSSECKeeper& dk, bool printHeader = true) {
983 if (printHeader) {
984 cout<<"Zone Type Act Pub Size Algorithm ID Location Keytag"<<endl;
985 cout<<"------------------------------------------------------------------------------------------"<<endl;
986 }
987 unsigned int spacelen = 0;
988 for (auto const &key : dk.getKeys(di.zone)) {
989 cout<<di.zone;
990 if (di.zone.toStringNoDot().length() > 29)
991 cout<<endl<<string(30, ' ');
992 else
993 cout<<string(30 - di.zone.toStringNoDot().length(), ' ');
994
995 cout<<DNSSECKeeper::keyTypeToString(key.second.keyType)<<" ";
996
997 if (key.second.active) {
998 cout << "Act ";
999 } else {
1000 cout << " ";
1001 }
1002
1003 if (key.second.published) {
1004 cout << "Pub ";
1005 } else {
1006 cout << " ";
1007 }
1008
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;
1012 continue;
1013 } else {
1014 cout<<key.first.getKey()->getBits()<<string(spacelen, ' ');
1015 }
1016
1017 string algname = DNSSECKeeper::algorithm2name(key.first.getAlgorithm());
1018 spacelen = (algname.length() >= 16) ? 1 : 16 - algname.length();
1019 cout<<algname<<string(spacelen, ' ');
1020
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, ' ');
1023
1024 #ifdef HAVE_P11KIT1
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")
1032 slot = elem.second;
1033 if (elem.first == "Label")
1034 label = elem.second;
1035 }
1036 if (engine.empty() || slot.empty()){
1037 cout<<"cryptokeys ";
1038 } else {
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, ' ');
1041 }
1042 #else
1043 cout<<"cryptokeys ";
1044 #endif
1045 cout<<key.first.getDNSKEY().getTag()<<endl;
1046 }
1047 }
1048
1049 static int listKeys(const string &zname, DNSSECKeeper& dk){
1050 UeberBackend B("default");
1051
1052 if (!zname.empty()) {
1053 DomainInfo di;
1054 if(!B.getDomainInfo(DNSName(zname), di)) {
1055 cerr << "Zone "<<zname<<" not found."<<endl;
1056 return EXIT_FAILURE;
1057 }
1058 listKey(di, dk);
1059 } else {
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;
1066 }
1067 }
1068 return EXIT_SUCCESS;
1069 }
1070
1071 static int listZone(const DNSName &zone) {
1072 UeberBackend B;
1073 DomainInfo di;
1074
1075 if (! B.getDomainInfo(zone, di)) {
1076 cerr << "Zone '" << zone << "' not found!" << endl;
1077 return EXIT_FAILURE;
1078 }
1079 di.backend->list(zone, di.id);
1080 DNSResourceRecord rr;
1081 cout<<"$ORIGIN ."<<endl;
1082 cout.sync_with_stdio(false);
1083
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, '.');
1088
1089 cout<<rr.qname<<"\t"<<rr.ttl<<"\tIN\t"<<rr.qtype.toString()<<"\t"<<rr.content<<"\n";
1090 }
1091 }
1092 cout.flush();
1093 return EXIT_SUCCESS;
1094 }
1095
1096 // lovingly copied from http://stackoverflow.com/questions/1798511/how-to-avoid-press-enter-with-any-getchar
1097 static int read1char(){
1098 int c;
1099 static struct termios oldt, newt;
1100
1101 /*tcgetattr gets the parameters of the current terminal
1102 STDIN_FILENO will tell tcgetattr that it should write the settings
1103 of stdin to oldt*/
1104 tcgetattr( STDIN_FILENO, &oldt);
1105 /*now the settings will be copied*/
1106 newt = oldt;
1107
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);
1111
1112 /*Those new settings will be set to STDIN
1113 TCSANOW tells tcsetattr to change attributes immediately. */
1114 tcsetattr( STDIN_FILENO, TCSANOW, &newt);
1115
1116 c=getchar();
1117
1118 /*restore the old settings*/
1119 tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
1120
1121 return c;
1122 }
1123
1124 static int clearZone(const DNSName &zone) {
1125 UeberBackend B;
1126 DomainInfo di;
1127
1128 if (! B.getDomainInfo(zone, di)) {
1129 cerr << "Zone '" << zone << "' not found!" << endl;
1130 return EXIT_FAILURE;
1131 }
1132 if(!di.backend->startTransaction(zone, di.id)) {
1133 cerr<<"Unable to start transaction for load of zone '"<<zone<<"'"<<endl;
1134 return EXIT_FAILURE;
1135 }
1136 di.backend->commitTransaction();
1137 return EXIT_SUCCESS;
1138 }
1139
1140 class PDNSColors
1141 {
1142 public:
1143 PDNSColors(bool nocolors)
1144 : d_colors(!nocolors && isatty(STDOUT_FILENO) && getenv("NO_COLORS") == nullptr)
1145 {
1146 }
1147 [[nodiscard]] string red() const
1148 {
1149 return d_colors ? "\x1b[31m" : "";
1150 }
1151 [[nodiscard]] string green() const
1152 {
1153 return d_colors ? "\x1b[32m" : "";
1154 }
1155 [[nodiscard]] string bold() const
1156 {
1157 return d_colors ? "\x1b[1m" : "";
1158 }
1159 [[nodiscard]] string rst() const
1160 {
1161 return d_colors ? "\x1b[0m" : "";
1162 }
1163 private:
1164 bool d_colors;
1165 };
1166
1167 static int editZone(const DNSName &zone, const PDNSColors& col) {
1168 UeberBackend B;
1169 DomainInfo di;
1170 DNSSECKeeper dk(&B);
1171
1172 if (! B.getDomainInfo(zone, di)) {
1173 cerr << "Zone '" << zone << "' not found!" << endl;
1174 return EXIT_FAILURE;
1175 }
1176
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
1180 not by other users.
1181 */
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);
1186 if(tmpfd < 0)
1187 unixDie("Making temporary filename in "+string(tmpnam));
1188 struct deleteme {
1189 ~deleteme() { unlink(d_name.c_str()); }
1190 deleteme(string name) : d_name(std::move(name)) {}
1191 string d_name;
1192 } dm(tmpnam);
1193
1194 vector<DNSResourceRecord> checkrr;
1195 int gotoline=0;
1196 string editor="editor";
1197 if(auto e=getenv("EDITOR")) // <3
1198 editor=e;
1199 string cmdline;
1200 editAgain:;
1201 di.backend->list(zone, di.id);
1202 pre.clear(); post.clear();
1203 {
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())
1212 continue;
1213 DNSRecord dr(rr);
1214 pre.push_back(dr);
1215 }
1216 sort(pre.begin(), pre.end(), DNSRecord::prettyCompare);
1217 for(const auto& dr : pre) {
1218 ostringstream os;
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");
1222 }
1223 close(tmpfd);
1224 tmpfd=-1;
1225 }
1226 editMore:;
1227 post.clear();
1228 cmdline=editor+" ";
1229 if(gotoline > 0)
1230 cmdline+="+"+std::to_string(gotoline)+" ";
1231 cmdline += tmpnam;
1232 int err=system(cmdline.c_str());
1233 if(err) {
1234 unixDie("Editing file with: '"+cmdline+"', perhaps set EDITOR variable");
1235 }
1236 cmdline.clear();
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;
1242 try {
1243 while(zpt.get(zrr)) {
1244 DNSRecord dr(zrr);
1245 post.push_back(dr);
1246 grouped[{dr.d_name,dr.d_type}].push_back(dr);
1247 }
1248 }
1249 catch(std::exception& e) {
1250 cerr<<"Problem: "<<e.what()<<" "<<zpt.getLineOfFile()<<endl;
1251 auto fnum = zpt.getLineNumAndFile();
1252 gotoline = fnum.second;
1253 goto reAsk;
1254 }
1255 catch(PDNSException& e) {
1256 cerr<<"Problem: "<<e.reason<<" "<<zpt.getLineOfFile()<<endl;
1257 auto fnum = zpt.getLineNumAndFile();
1258 gotoline = fnum.second;
1259 goto reAsk;
1260 }
1261
1262 sort(post.begin(), post.end(), DNSRecord::prettyCompare);
1263 checkrr.clear();
1264
1265 for(const DNSRecord& rr : post) {
1266 DNSResourceRecord drr = DNSResourceRecord::fromWire(rr);
1267 drr.domain_id = di.id;
1268 checkrr.push_back(drr);
1269 }
1270 if(checkZone(dk, B, zone, &checkrr)) {
1271 reAsk:;
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;
1273 int c=read1char();
1274 cerr<<"\n";
1275 if(c=='e') {
1276 post.clear();
1277 goto editMore;
1278 } else if(c=='r') {
1279 post.clear();
1280 goto editAgain;
1281 } else if(c=='q') {
1282 return EXIT_FAILURE;
1283 } else if(c!='a') {
1284 goto reAsk;
1285 }
1286 }
1287
1288
1289 vector<DNSRecord> diff;
1290
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) {
1294 ostringstream str;
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();
1297
1298 }
1299 diff.clear();
1300 set_difference(post.cbegin(), post.cend(), pre.cbegin(), pre.cend(), back_inserter(diff), DNSRecord::prettyCompare);
1301 for(const auto& d : diff) {
1302 ostringstream str;
1303
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();
1306 }
1307 cout<<"Detected the following changes:"<<endl;
1308 for(const auto& c : changed) {
1309 cout<<c.second;
1310 }
1311 if (!changed.empty()) {
1312 if (changed.find({zone, QType::SOA}) == changed.end()) {
1313 reAsk3:;
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();
1317 switch(c) {
1318 case 'y':
1319 {
1320 DNSRecord oldSoaDR = grouped[{zone, QType::SOA}].at(0); // there should be only one SOA record, so we can use .at(0);
1321 ostringstream str;
1322 str<< col.red() << "-" << oldSoaDR.d_name << " " << oldSoaDR.d_ttl << " IN " << DNSRecordContent::NumberToType(oldSoaDR.d_type) << " " <<oldSoaDR.getContent()->getZoneRepresentation(true) << col.rst() <<endl;
1323
1324 SOAData sd;
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?
1327
1328 string soaEditKind;
1329 dk.getSoaEdit(zone, soaEditKind);
1330
1331 DNSResourceRecord rr;
1332 makeIncreasedSOARecord(sd, "SOA-EDIT-INCREASE", soaEditKind, rr);
1333 DNSRecord dr(rr);
1334 str << col.green() << "+" << dr.d_name << " " << dr.d_ttl<< " IN " <<DNSRecordContent::NumberToType(dr.d_type) << " " <<dr.getContent()->getZoneRepresentation(true) << col.rst() <<endl;
1335
1336 changed[{dr.d_name, dr.d_type}]+=str.str();
1337 grouped[{dr.d_name, dr.d_type}].at(0) = dr;
1338 }
1339 break;
1340 case 'q':
1341 return EXIT_FAILURE;
1342 case 'e':
1343 goto editMore;
1344 case 'n':
1345 goto reAsk2;
1346 default:
1347 goto reAsk3;
1348 }
1349 }
1350 }
1351 reAsk2:;
1352 if(changed.empty()) {
1353 cout<<endl<<"No changes to apply."<<endl;
1354 return(EXIT_SUCCESS);
1355 }
1356 cout<<endl<<"(a)pply these changes, (e)dit again, (r)etry with original zone, (q)uit: "<<std::flush;
1357 int c=read1char();
1358 post.clear();
1359 cerr<<'\n';
1360 if(c=='q')
1361 return(EXIT_SUCCESS);
1362 else if(c=='e')
1363 goto editMore;
1364 else if(c=='r')
1365 goto editAgain;
1366 else if(changed.empty() || c!='a')
1367 goto reAsk2;
1368
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;
1375 vrr.push_back(crr);
1376 }
1377 di.backend->replaceRRSet(di.id, change.first.first, QType(change.first.second), vrr);
1378 }
1379 rectifyZone(dk, zone, false, false);
1380 di.backend->commitTransaction();
1381 return EXIT_SUCCESS;
1382 }
1383
1384 #ifdef HAVE_IPCIPHER
1385 static int xcryptIP(const std::string& cmd, const std::string& ip, const std::string& rkey)
1386 {
1387
1388 ComboAddress ca(ip), ret;
1389
1390 if(cmd=="ipencrypt")
1391 ret = encryptCA(ca, rkey);
1392 else
1393 ret = decryptCA(ca, rkey);
1394
1395 cout<<ret.toString()<<endl;
1396 return EXIT_SUCCESS;
1397 }
1398 #endif /* HAVE_IPCIPHER */
1399
1400 static int zonemdVerifyFile(const DNSName& zone, const string& fname) {
1401 ZoneParserTNG zpt(fname, zone, "", true);
1402 zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
1403
1404 bool validationDone, validationOK;
1405
1406 try {
1407 auto zoneMD = pdns::ZoneMD(zone);
1408 zoneMD.readRecords(zpt);
1409 zoneMD.verify(validationDone, validationOK);
1410 }
1411 catch (const PDNSException& ex) {
1412 cerr << "zonemd-verify-file: " << ex.reason << endl;
1413 return EXIT_FAILURE;
1414 }
1415 catch (const std::exception& ex) {
1416 cerr << "zonemd-verify-file: " << ex.what() << endl;
1417 return EXIT_FAILURE;
1418 }
1419
1420 if (validationDone) {
1421 if (validationOK) {
1422 cout << "zonemd-verify-file: Verification of ZONEMD record succeeded" << endl;
1423 return EXIT_SUCCESS;
1424 } else {
1425 cerr << "zonemd-verify-file: Verification of ZONEMD record(s) failed" << endl;
1426 }
1427 }
1428 else {
1429 cerr << "zonemd-verify-file: No suitable ZONEMD record found to verify against" << endl;
1430 }
1431 return EXIT_FAILURE;
1432 }
1433
1434 static int loadZone(const DNSName& zone, const string& fname) {
1435 UeberBackend B;
1436 DomainInfo di;
1437
1438 if (B.getDomainInfo(zone, di)) {
1439 cerr << "Zone '" << zone << "' exists already, replacing contents" << endl;
1440 }
1441 else {
1442 cerr<<"Creating '"<<zone<<"'"<<endl;
1443 B.createDomain(zone, DomainInfo::Native, vector<ComboAddress>(), "");
1444
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;
1448 }
1449 }
1450 DNSBackend* db = di.backend;
1451 ZoneParserTNG zpt(fname, zone);
1452 zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
1453
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;
1458 }
1459 rr.domain_id=di.id;
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;
1465 }
1466 if (rr.qtype == QType::SOA) {
1467 if (haveSOA)
1468 continue;
1469 else
1470 haveSOA = true;
1471 }
1472 try {
1473 DNSRecordContent::make(rr.qtype, QClass::IN, rr.content);
1474 }
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;
1478 }
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;
1482 }
1483 db->feedRecord(rr, DNSName());
1484 }
1485 db->commitTransaction();
1486 return EXIT_SUCCESS;
1487 }
1488
1489 static int createZone(const DNSName &zone, const DNSName& nsname) {
1490 UeberBackend B;
1491 DomainInfo di;
1492 if (B.getDomainInfo(zone, di)) {
1493 cerr << "Zone '" << zone << "' exists already" << endl;
1494 return EXIT_FAILURE;
1495 }
1496
1497 DNSResourceRecord rr;
1498 rr.qname = zone;
1499 rr.auth = true;
1500 rr.ttl = ::arg().asNum("default-ttl");
1501 rr.qtype = "SOA";
1502
1503 string soa = ::arg()["default-soa-content"];
1504 boost::replace_all(soa, "@", zone.toStringNoDot());
1505 SOAData sd;
1506 try {
1507 fillSOAData(soa, sd);
1508 }
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;
1513 }
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;
1518 }
1519
1520 rr.content = makeSOAContent(sd)->getZoneRepresentation(true);
1521
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;
1527 }
1528
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;
1534 rr.qtype=QType::NS;
1535 rr.content=nsname.toStringNoDot();
1536 di.backend->feedRecord(rr, DNSName());
1537 }
1538
1539 di.backend->commitTransaction();
1540
1541 return EXIT_SUCCESS;
1542 }
1543
1544 static int createSecondaryZone(const vector<string>& cmds)
1545 {
1546 UeberBackend B;
1547 DomainInfo di;
1548 DNSName zone(cmds.at(1));
1549 if (B.getDomainInfo(zone, di)) {
1550 cerr << "Zone '" << zone << "' exists already" << endl;
1551 return EXIT_FAILURE;
1552 }
1553 vector<ComboAddress> primaries;
1554 for (unsigned i=2; i < cmds.size(); i++) {
1555 primaries.emplace_back(cmds.at(i), 53);
1556 }
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;
1562 }
1563 return EXIT_SUCCESS;
1564 }
1565
1566 static int changeSecondaryZonePrimary(const vector<string>& cmds)
1567 {
1568 UeberBackend B;
1569 DomainInfo di;
1570 DNSName zone(cmds.at(1));
1571 if (!B.getDomainInfo(zone, di)) {
1572 cerr << "Zone '" << zone << "' doesn't exist" << endl;
1573 return EXIT_FAILURE;
1574 }
1575 vector<ComboAddress> primaries;
1576 for (unsigned i=2; i < cmds.size(); i++) {
1577 primaries.emplace_back(cmds.at(i), 53);
1578 }
1579 cerr << "Updating secondary zone '" << zone << "', primaries to '" << comboAddressVecToString(primaries) << "'" << endl;
1580 try {
1581 di.backend->setPrimaries(zone, primaries);
1582 return EXIT_SUCCESS;
1583 }
1584 catch (PDNSException& e) {
1585 cerr << "Setting primary for zone '" << zone << "' failed: " << e.reason << endl;
1586 return EXIT_FAILURE;
1587 }
1588 }
1589
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));
1595 DNSName name;
1596 if (cmds.at(2) == "@")
1597 name=zone;
1598 else
1599 name = DNSName(cmds.at(2)) + zone;
1600
1601 rr.qtype = DNSRecordContent::TypeToNumber(cmds.at(3));
1602 rr.ttl = ::arg().asNum("default-ttl");
1603
1604 UeberBackend B;
1605 DomainInfo di;
1606 if(!B.getDomainInfo(zone, di)) {
1607 cerr << "Zone '" << zone << "' does not exist" << endl;
1608 return EXIT_FAILURE;
1609 }
1610 rr.auth = true;
1611 rr.domain_id = di.id;
1612 rr.qname = name;
1613 DNSResourceRecord oldrr;
1614
1615 di.backend->startTransaction(zone, -1);
1616
1617 if(addOrReplace) { // the 'add' case
1618 di.backend->lookup(rr.qtype, rr.qname, di.id);
1619
1620 while(di.backend->get(oldrr))
1621 newrrs.push_back(oldrr);
1622 }
1623
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)) {
1628 contentStart++;
1629 }
1630 else {
1631 rr.ttl = ::arg().asNum("default-ttl");
1632 }
1633 }
1634
1635 di.backend->lookup(QType(QType::ANY), rr.qname, di.id);
1636 bool found=false;
1637 if(rr.qtype.getCode() == QType::CNAME) { // this will save us SO many questions
1638
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
1641 found=true;
1642 }
1643 if(found) {
1644 cerr<<"Attempting to add CNAME to "<<rr.qname<<" which already had existing records"<<endl;
1645 return EXIT_FAILURE;
1646 }
1647 }
1648 else {
1649 while(di.backend->get(oldrr)) {
1650 if(oldrr.qtype.getCode() == QType::CNAME)
1651 found=true;
1652 }
1653 if(found) {
1654 cerr<<"Attempting to add record to "<<rr.qname<<" which already had a CNAME record"<<endl;
1655 return EXIT_FAILURE;
1656 }
1657 }
1658
1659 if(!addOrReplace) {
1660 cout<<"Current records for "<<rr.qname<<" IN "<<rr.qtype.toString()<<" will be replaced"<<endl;
1661 }
1662 for(auto i = contentStart ; i < cmds.size() ; ++i) {
1663 rr.content = DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, cmds.at(i))->getZoneRepresentation(true);
1664
1665 newrrs.push_back(rr);
1666 }
1667
1668
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;
1672 }
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;
1678 }
1679 di.backend->commitTransaction();
1680 return EXIT_SUCCESS;
1681 }
1682
1683 // addAutoPrimary add a new autoprimary
1684 static int addAutoPrimary(const std::string& IP, const std::string& nameserver, const std::string& account)
1685 {
1686 UeberBackend B("default");
1687 const AutoPrimary primary(IP, nameserver, account);
1688 if (B.autoPrimaryAdd(primary)) {
1689 return EXIT_SUCCESS;
1690 }
1691 cerr<<"could not find a backend with autosecondary support"<<endl;
1692 return EXIT_FAILURE;
1693 }
1694
1695 static int removeAutoPrimary(const std::string &IP, const std::string &nameserver)
1696 {
1697 UeberBackend B("default");
1698 const AutoPrimary primary(IP, nameserver, "");
1699 if ( B.autoPrimaryRemove(primary) ){
1700 return EXIT_SUCCESS;
1701 }
1702 cerr<<"could not find a backend with autosecondary support"<<endl;
1703 return EXIT_FAILURE;
1704 }
1705
1706 static int listAutoPrimaries()
1707 {
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;
1713 }
1714
1715 for(const auto& primary: primaries) {
1716 cout<<"IP="<<primary.ip<<", NS="<<primary.nameserver<<", account="<<primary.account<<endl;
1717 }
1718
1719 return EXIT_SUCCESS;
1720 }
1721
1722 // delete-rrset zone name type
1723 static int deleteRRSet(const std::string& zone_, const std::string& name_, const std::string& type_)
1724 {
1725 UeberBackend B;
1726 DomainInfo di;
1727 DNSName zone(zone_);
1728 if(!B.getDomainInfo(zone, di)) {
1729 cerr << "Zone '" << zone << "' does not exist" << endl;
1730 return EXIT_FAILURE;
1731 }
1732
1733 DNSName name;
1734 if(name_=="@")
1735 name=zone;
1736 else
1737 name=DNSName(name_)+zone;
1738
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;
1744 }
1745
1746 static int listAllZones(const string &type="") {
1747
1748 int kindFilter = -1;
1749 if (!type.empty()) {
1750 if (toUpper(type) == "PRIMARY" || toUpper(type) == "MASTER")
1751 kindFilter = 0;
1752 else if (toUpper(type) == "SECONDARY" || toUpper(type) == "SLAVE")
1753 kindFilter = 1;
1754 else if (toUpper(type) == "NATIVE")
1755 kindFilter = 2;
1756 else if (toUpper(type) == "PRODUCER")
1757 kindFilter = 3;
1758 else if (toUpper(type) == "CONSUMER")
1759 kindFilter = 4;
1760 else {
1761 cerr << "Syntax: pdnsutil list-all-zones [primary|secondary|native|producer|consumer]" << endl;
1762 return 1;
1763 }
1764 }
1765
1766 UeberBackend B("default");
1767
1768 vector<DomainInfo> domains;
1769 B.getAllDomains(&domains, false, g_verbose);
1770
1771 int count = 0;
1772 for (const auto& di: domains) {
1773 if (di.kind == kindFilter || kindFilter == -1) {
1774 cout<<di.zone<<endl;
1775 count++;
1776 }
1777 }
1778
1779 if (g_verbose) {
1780 if (kindFilter != -1)
1781 cout<<type<<" zonecount: "<<count<<endl;
1782 else
1783 cout<<"All zonecount: "<<count<<endl;
1784 }
1785
1786 return 0;
1787 }
1788
1789 static int listMemberZones(const string& catalog)
1790 {
1791
1792 UeberBackend B("default");
1793
1794 DNSName catz(catalog);
1795 DomainInfo di;
1796 if (!B.getDomainInfo(catz, di)) {
1797 cerr << "Zone '" << catz << "' not found" << endl;
1798 return EXIT_FAILURE;
1799 }
1800 if (!di.isCatalogType()) {
1801 cerr << "Zone '" << catz << "' is not a catalog zone" << endl;
1802 return EXIT_FAILURE;
1803 }
1804
1805 CatalogInfo::CatalogType type;
1806 if (di.kind == DomainInfo::Producer) {
1807 type = CatalogInfo::Producer;
1808 }
1809 else {
1810 type = CatalogInfo::Consumer;
1811 }
1812
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;
1817 }
1818
1819 for (const auto& ci : members) {
1820 cout << ci.d_zone << endl;
1821 }
1822
1823 if (g_verbose) {
1824 cout << "All zonecount: " << members.size() << endl;
1825 }
1826
1827 return EXIT_SUCCESS;
1828 }
1829
1830 static bool testAlgorithm(int algo)
1831 {
1832 return DNSCryptoKeyEngine::testOne(algo);
1833 }
1834
1835 static bool testAlgorithms()
1836 {
1837 return DNSCryptoKeyEngine::testAll();
1838 }
1839
1840 static void testSpeed(const DNSName& zone, const string& /* remote */, int cores)
1841 {
1842 DNSResourceRecord rr;
1843 rr.qname=DNSName("blah")+zone;
1844 rr.qtype=QType::A;
1845 rr.ttl=3600;
1846 rr.auth=true;
1847 rr.qclass = QClass::IN;
1848
1849 UeberBackend db("key-only");
1850
1851 if ( db.backends.empty() )
1852 {
1853 throw runtime_error("No backends available for DNSSEC key storage");
1854 }
1855
1856 ChunkedSigningPipe csp(DNSName(zone), true, cores, 100);
1857
1858 vector<DNSZoneRecord> signatures;
1859 uint32_t rnd;
1860 unsigned char* octets = (unsigned char*)&rnd;
1861 char tmp[25];
1862 DTime dt;
1863 dt.set();
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]);
1868 rr.content=tmp;
1869
1870 snprintf(tmp, sizeof(tmp), "r-%u", rnd);
1871 rr.qname=DNSName(tmp)+zone;
1872 DNSZoneRecord dzr;
1873 dzr.dr=DNSRecord(rr);
1874 if(csp.submit(dzr))
1875 while(signatures = csp.getChunk(), !signatures.empty())
1876 ;
1877 }
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())
1881 ;
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;
1884 }
1885
1886 static void verifyCrypto(const string& zone)
1887 {
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;
1900 apex=rr.qname;
1901 drc = *std::dynamic_pointer_cast<DNSKEYRecordContent>(DNSRecordContent::make(QType::DNSKEY, QClass::IN, rr.content));
1902 }
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));
1906 }
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));
1910 }
1911 else {
1912 qname = rr.qname;
1913 toSign.insert(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
1914 }
1915 }
1916
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;
1922 }
1923 }
1924
1925 static bool disableDNSSECOnZone(DNSSECKeeper& dk, const DNSName& zone)
1926 {
1927 UeberBackend B("default");
1928 DomainInfo di;
1929
1930 if (!B.getDomainInfo(zone, di)){
1931 cerr << "No such zone in the database" << endl;
1932 return false;
1933 }
1934
1935 string error, info;
1936 bool ret = dk.unSecureZone(zone, error);
1937 if (!ret) {
1938 cerr << error << endl;
1939 }
1940 return ret;
1941 }
1942
1943 static int setZoneOptionsJson(const DNSName& zone, const string& options)
1944 {
1945 UeberBackend B("default");
1946 DomainInfo di;
1947
1948 if (!B.getDomainInfo(zone, di)) {
1949 cerr << "No such zone " << zone << " in the database" << endl;
1950 return EXIT_FAILURE;
1951 }
1952 if (!di.backend->setOptions(zone, options)) {
1953 cerr << "Could not find backend willing to accept new zone configuration" << endl;
1954 return EXIT_FAILURE;
1955 }
1956 return EXIT_SUCCESS;
1957 }
1958
1959 static int setZoneOption(const DNSName& zone, const string& type, const string& option, const set<string>& values)
1960 {
1961 UeberBackend B("default");
1962 DomainInfo di;
1963 CatalogInfo ci;
1964
1965 if (!B.getDomainInfo(zone, di)) {
1966 cerr << "No such zone " << zone << " in the database" << endl;
1967 return EXIT_FAILURE;
1968 }
1969
1970 CatalogInfo::CatalogType ctype;
1971 if (type == "producer") {
1972 ctype = CatalogInfo::CatalogType::Producer;
1973 }
1974 else {
1975 ctype = CatalogInfo::CatalogType::Consumer;
1976 }
1977
1978 ci.fromJson(di.options, ctype);
1979
1980 if (option == "coo") {
1981 ci.d_coo = (!values.empty() ? DNSName(*values.begin()) : DNSName());
1982 }
1983 else if (option == "unique") {
1984 ci.d_unique = (!values.empty() ? DNSName(*values.begin()) : DNSName());
1985 }
1986 else if (option == "group") {
1987 ci.d_group = values;
1988 }
1989
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;
1993 }
1994
1995 return EXIT_SUCCESS;
1996 }
1997
1998 static int setZoneCatalog(const DNSName& zone, const DNSName& catalog)
1999 {
2000 UeberBackend B("default");
2001 DomainInfo di;
2002
2003 if (!B.getDomainInfo(zone, di)) {
2004 cerr << "No such zone " << zone << " in the database" << endl;
2005 return EXIT_FAILURE;
2006 }
2007 if (!di.backend->setCatalog(zone, catalog)) {
2008 cerr << "Could not find backend willing to accept new zone configuration" << endl;
2009 return EXIT_FAILURE;
2010 }
2011 return EXIT_SUCCESS;
2012 }
2013
2014 static int setZoneAccount(const DNSName& zone, const string &account)
2015 {
2016 UeberBackend B("default");
2017 DomainInfo di;
2018
2019 if (!B.getDomainInfo(zone, di)){
2020 cerr << "No such zone "<<zone<<" in the database" << endl;
2021 return EXIT_FAILURE;
2022 }
2023 if(!di.backend->setAccount(zone, account)) {
2024 cerr<<"Could not find backend willing to accept new zone configuration"<<endl;
2025 return EXIT_FAILURE;
2026 }
2027 return EXIT_SUCCESS;
2028 }
2029
2030 static int setZoneKind(const DNSName& zone, const DomainInfo::DomainKind kind)
2031 {
2032 UeberBackend B("default");
2033 DomainInfo di;
2034
2035 if (!B.getDomainInfo(zone, di)){
2036 cerr << "No such zone "<<zone<<" in the database" << endl;
2037 return EXIT_FAILURE;
2038 }
2039 if(!di.backend->setKind(zone, kind)) {
2040 cerr<<"Could not find backend willing to accept new zone configuration"<<endl;
2041 return EXIT_FAILURE;
2042 }
2043 return EXIT_SUCCESS;
2044 }
2045
2046 static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
2047 {
2048 UeberBackend B("default");
2049 DomainInfo di;
2050
2051 if (!B.getDomainInfo(zone, di)){
2052 cerr << "No such zone in the database" << endl;
2053 return false;
2054 }
2055
2056 if (!di.account.empty()) {
2057 cout<<"This zone is owned by "<<di.account<<endl;
2058 }
2059 if (!exportDS) {
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<<" ";
2063 SOAData sd;
2064 if(B.getSOAUncached(zone, sd)) {
2065 if(sd.serial == di.notified_serial)
2066 cout<< "== ";
2067 else
2068 cout << "!= ";
2069 cout<<sd.serial<<" (serial in the database)"<<endl;
2070 }
2071 }
2072 else if (di.isSecondaryType()) {
2073 cout << "Primar" << addS(di.primaries, "y", "ies") << ": ";
2074 for (const auto& m : di.primaries)
2075 cout<<m.toStringWithPort()<<" ";
2076 cout<<endl;
2077 struct tm tm;
2078 localtime_r(&di.last_check, &tm);
2079 char buf[80];
2080 if(di.last_check)
2081 strftime(buf, sizeof(buf)-1, "%a %F %H:%M:%S", &tm);
2082 else
2083 strncpy(buf, "Never", sizeof(buf)-1);
2084 buf[sizeof(buf)-1] = '\0';
2085 cout << "Last time we got update from primary: " << buf << endl;
2086 SOAData sd;
2087 if(B.getSOAUncached(zone, sd)) {
2088 cout<<"SOA serial in database: "<<sd.serial<<endl;
2089 cout<<"Refresh interval: "<<sd.refresh<<" seconds"<<endl;
2090 }
2091 else
2092 cout<<"No SOA serial found in database"<<endl;
2093 }
2094 }
2095
2096 if(!dk.isSecuredZone(zone)) {
2097 auto &outstream = (exportDS ? cerr : cout);
2098 outstream << "Zone is not actively secured" << endl;
2099 if (exportDS) {
2100 // it does not make sense to proceed here, and it might be useful
2101 // for scripts to know that something is odd here
2102 return false;
2103 }
2104 }
2105
2106 NSEC3PARAMRecordContent ns3pr;
2107 bool narrow = false;
2108 bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
2109
2110 DNSSECKeeper::keyset_t keyset=dk.getKeys(zone);
2111
2112 if (!exportDS) {
2113 std::vector<std::string> meta;
2114
2115 if (B.getDomainMetadata(zone, "TSIG-ALLOW-AXFR", meta) && !meta.empty()) {
2116 cout << "Zone has following allowed TSIG key(s): " << boost::join(meta, ",") << endl;
2117 }
2118
2119 meta.clear();
2120 if (B.getDomainMetadata(zone, "AXFR-MASTER-TSIG", meta) && !meta.empty()) {
2121 cout << "Zone uses following TSIG key(s): " << boost::join(meta, ",") << endl;
2122 }
2123
2124 std::map<std::string, std::vector<std::string> > metamap;
2125 if(B.getAllDomainMetadata(zone, metamap)) {
2126 cout<<"Metadata items: ";
2127 if(metamap.empty())
2128 cout<<"None";
2129 cout<<endl;
2130
2131 for(const auto& m : metamap) {
2132 for(const auto& i : m.second)
2133 cout << '\t' << m.first<<'\t' << i <<endl;
2134 }
2135 }
2136
2137 }
2138
2139 if (dk.isPresigned(zone)) {
2140 if (!exportDS) {
2141 cout <<"Zone is presigned"<<endl;
2142 }
2143
2144 // get us some keys
2145 vector<DNSKEYRecordContent> keys;
2146 DNSZoneRecord zr;
2147
2148 di.backend->lookup(QType(QType::DNSKEY), zone, di.id );
2149 while(di.backend->get(zr)) {
2150 keys.push_back(*getRR<DNSKEYRecordContent>(zr.dr));
2151 }
2152
2153 if(keys.empty()) {
2154 cerr << "No keys for zone '"<<zone<<"'."<<endl;
2155 return true;
2156 }
2157
2158 if (!exportDS) {
2159 if(!haveNSEC3)
2160 cout<<"Zone has NSEC semantics"<<endl;
2161 else
2162 cout<<"Zone has " << (narrow ? "NARROW " : "") <<"hashed NSEC3 semantics, configuration: "<<ns3pr.getZoneRepresentation()<<endl;
2163 cout << "keys: "<<endl;
2164 }
2165
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);
2170
2171 int bits = -1;
2172 try {
2173 auto engine = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key); // throws on unknown algo or bad key
2174 bits=engine->getBits();
2175 }
2176 catch (const std::exception& e) {
2177 cerr<<"Could not process key to extract metadata: "<<e.what()<<endl;
2178 }
2179 if (!exportDS) {
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;
2182 }
2183
2184 const std::string prefix(exportDS ? "" : "DS = ");
2185 if (g_verbose) {
2186 cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
2187 }
2188 cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA256).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
2189 try {
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;
2192 }
2193 catch(...)
2194 {}
2195 try {
2196 string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA384).getZoneRepresentation();
2197 cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( SHA-384 digest )" << endl;
2198 }
2199 catch(...)
2200 {}
2201 }
2202 }
2203 else if(keyset.empty()) {
2204 cerr << "No keys for zone '"<<zone<<"'."<<endl;
2205 }
2206 else {
2207 if (!exportDS) {
2208 if(!haveNSEC3)
2209 cout<<"Zone has NSEC semantics"<<endl;
2210 else
2211 cout<<"Zone has " << (narrow ? "NARROW " : "") <<"hashed NSEC3 semantics, configuration: "<<ns3pr.getZoneRepresentation()<<endl;
2212 cout << "keys: "<<endl;
2213 }
2214
2215 for(const DNSSECKeeper::keyset_t::value_type& value : keyset) {
2216 string algname = DNSSECKeeper::algorithm2name(value.first.getAlgorithm());
2217 if (!exportDS) {
2218 cout<<"ID = "<<value.second.id<<" ("<<DNSSECKeeper::keyTypeToString(value.second.keyType)<<")";
2219 }
2220 if (value.first.getKey()->getBits() < 1) {
2221 cout<<" <key missing or defunct, perhaps you should run pdnsutil hsm create-key>" <<endl;
2222 continue;
2223 }
2224 if (!exportDS) {
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;
2228 }
2229
2230 if (!exportDS) {
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;
2233 }
2234 }
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 = ");
2238 if (g_verbose) {
2239 cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
2240 }
2241 cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA256).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
2242 try {
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;
2245 }
2246 catch(...)
2247 {}
2248 try {
2249 string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA384).getZoneRepresentation();
2250 cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( SHA-384 digest )" << endl;
2251 }
2252 catch(...)
2253 {}
2254 }
2255 }
2256 }
2257 if (!di.options.empty()) {
2258 cout << "Options:" << endl;
2259 cout << di.options << endl;
2260 }
2261 if (!di.catalog.empty()) {
2262 cout << "Catalog: " << endl;
2263 cout << di.catalog << endl;
2264 }
2265 return true;
2266 }
2267
2268 static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
2269 {
2270 // temp var for addKey
2271 int64_t id{-1};
2272
2273 // parse attribute
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");
2278
2279 if (k_size < 0) {
2280 throw runtime_error("KSK key size must be equal to or greater than 0");
2281 }
2282
2283 if (k_algo.empty() && z_algo.empty()) {
2284 throw runtime_error("Zero algorithms given for KSK+ZSK in total");
2285 }
2286
2287 if (z_size < 0) {
2288 throw runtime_error("ZSK key size must be equal to or greater than 0");
2289 }
2290
2291 if(dk.isSecuredZone(zone)) {
2292 cerr << "Zone '"<<zone<<"' already secure, remove keys with pdnsutil remove-zone-key if needed"<<endl;
2293 return false;
2294 }
2295
2296 DomainInfo di;
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;
2301 return false;
2302 }
2303
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;
2307 }
2308
2309 if (!k_algo.empty()) { // Add a KSK
2310 if (k_size)
2311 cout << "Securing zone with key size " << k_size << endl;
2312 else
2313 cout << "Securing zone with default key size" << endl;
2314
2315 cout << "Adding " << (z_algo.empty() ? "CSK (257)" : "KSK") << " with algorithm " << k_algo << endl;
2316
2317 int k_real_algo = DNSSECKeeper::shorthand2algorithm(k_algo);
2318
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;
2324 return false;
2325 }
2326 }
2327
2328 if (!z_algo.empty()) {
2329 cout << "Adding " << (k_algo.empty() ? "CSK (256)" : "ZSK") << " with algorithm " << z_algo << endl;
2330
2331 int z_real_algo = DNSSECKeeper::shorthand2algorithm(z_algo);
2332
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;
2338 return false;
2339 }
2340 }
2341
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;
2348 return false;
2349 }
2350
2351 // rectifyZone(dk, zone);
2352 // showZone(dk, zone);
2353 cout<<"Zone "<<zone<<" secured"<<endl;
2354 return true;
2355 }
2356
2357 static int testSchema(DNSSECKeeper& dk, const DNSName& zone)
2358 {
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;
2361 cout<<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;
2370
2371 DomainInfo di;
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;
2376 }
2377 db=di.backend;
2378 DNSResourceRecord rr, rrget;
2379 cout<<"Starting transaction to feed records"<<endl;
2380 db->startTransaction(zone, di.id);
2381
2382 rr.qtype=QType::SOA;
2383 rr.qname=zone;
2384 rr.ttl=86400;
2385 rr.domain_id=di.id;
2386 rr.auth=true;
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;
2391 // 300 As
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);
2399 if(db->get(rrget))
2400 {
2401 DNSResourceRecord rrthrowaway;
2402 if(db->get(rrthrowaway)) // should not touch rr but don't assume anything
2403 {
2404 cout<<"Expected one record, got multiple, aborting"<<endl;
2405 return EXIT_FAILURE;
2406 }
2407 int size=rrget.content.size();
2408 if(size != 302)
2409 {
2410 cout<<"Expected 302 bytes, got "<<size<<", aborting"<<endl;
2411 return EXIT_FAILURE;
2412 }
2413 }
2414 cout<<"[+] content field is over 255 bytes"<<endl;
2415
2416 cout<<"Dropping all records, inserting SOA+2xA"<<endl;
2417 db->startTransaction(zone, di.id);
2418
2419 rr.qtype=QType::SOA;
2420 rr.qname=zone;
2421 rr.ttl=86400;
2422 rr.domain_id=di.id;
2423 rr.auth=true;
2424 rr.content="ns1.example.com. ahu.example.com. 2012081039 7200 3600 1209600 3600";
2425 cout<<"Feeding SOA"<<endl;
2426 db->feedRecord(rr, DNSName());
2427
2428 rr.qtype=QType::A;
2429 rr.qname=DNSName("_underscore")+zone;
2430 rr.content="127.0.0.1";
2431 db->feedRecord(rr, DNSName());
2432
2433 rr.qname=DNSName("bla")+zone;
2434 cout<<"Committing"<<endl;
2435 db->commitTransaction();
2436
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)
2446 {
2447 cout<<"before is wrong, got '"<<before.toString()<<"', expected '_underscore."<<zone.toString()<<"', aborting"<<endl;
2448 return EXIT_FAILURE;
2449 }
2450 if(after != zone)
2451 {
2452 cout<<"after is wrong, got '"<<after.toString()<<"', expected '"<<zone.toString()<<"', aborting"<<endl;
2453 return EXIT_FAILURE;
2454 }
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;
2462 }
2463 cout<<"Setting serial that needs 32 bits"<<endl;
2464 try {
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;
2470 }
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;
2475 } else {
2476 cout<<"[+] Big serials work correctly"<<endl;
2477 }
2478 cout<<endl;
2479 cout << "End of tests, please remove " << zone << " from zones+records" << endl;
2480
2481 return EXIT_SUCCESS;
2482 }
2483
2484 static int addOrSetMeta(const DNSName& zone, const string& kind, const vector<string>& values, bool clobber) {
2485 UeberBackend B("default");
2486 DomainInfo di;
2487
2488 if (!B.getDomainInfo(zone, di)) {
2489 cerr << "Invalid zone '" << zone << "'" << endl;
2490 return 1;
2491 }
2492
2493 vector<string> all_metadata;
2494
2495 if (!clobber) {
2496 B.getDomainMetadata(zone, kind, all_metadata);
2497 }
2498
2499 all_metadata.insert(all_metadata.end(), values.begin(), values.end());
2500
2501 if (!B.setDomainMetadata(zone, kind, all_metadata)) {
2502 cerr << "Unable to set meta for '" << zone << "'" << endl;
2503 return 1;
2504 }
2505
2506 cout << "Set '" << zone << "' meta " << kind << " = " << boost::join(all_metadata, ", ") << endl;
2507 return 0;
2508 }
2509
2510 // NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Clean this function up.
2511 int main(int argc, char** argv)
2512 try
2513 {
2514 po::options_description desc("Allowed options");
2515 desc.add_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> >());
2524
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);
2528 po::notify(g_vm);
2529
2530 vector<string> cmds;
2531
2532 if(g_vm.count("commands"))
2533 cmds = g_vm["commands"].as<vector<string> >();
2534
2535 g_verbose = g_vm.count("verbose");
2536
2537 if (g_vm.count("version")) {
2538 cout<<"pdnsutil "<<VERSION<<endl;
2539 return 0;
2540 }
2541
2542 if (cmds.empty() || g_vm.count("help") || cmds.at(0) == "help") {
2543 cout << "Usage: \npdnsutil [options] <command> [params ..]\n"
2544 << endl;
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)
2558 cout << "|ed25519";
2559 #endif
2560 #if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
2561 cout << "|ed448";
2562 #endif
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;
2596 #ifdef HAVE_P11KIT1
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;
2601 #endif
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;
2619
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;
2658
2659 return 0;
2660 }
2661
2662 loadMainConfig(g_vm["config-dir"].as<string>());
2663
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
2666 return 0;
2667 }
2668 if (cmds.at(0) == "test-algorithm") {
2669 if(cmds.size() != 2) {
2670 cerr << "Syntax: pdnsutil test-algorithm algonum"<<endl;
2671 return 0;
2672 }
2673 if (testAlgorithm(pdns::checked_stoi<int>(cmds.at(1))))
2674 return 0;
2675 return 1;
2676 }
2677
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;
2681 return 0;
2682 }
2683 #ifdef HAVE_IPCIPHER
2684 string key;
2685 if(cmds.size()==4) {
2686 if (B64Decode(cmds.at(2), key) < 0) {
2687 cerr << "Could not parse '" << cmds.at(3) << "' as base64" << endl;
2688 return 0;
2689 }
2690 }
2691 else {
2692 key = makeIPCipherKey(cmds.at(2));
2693 }
2694 exit(xcryptIP(cmds.at(0), cmds.at(1), key));
2695 #else
2696 cerr<<cmds.at(0)<<" requires ipcipher support which is not available"<<endl;
2697 return 0;
2698 #endif /* HAVE_IPCIPHER */
2699 }
2700
2701 if (cmds.at(0) == "test-algorithms") {
2702 if (testAlgorithms())
2703 return 0;
2704 return 1;
2705 }
2706
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;
2710 return 1;
2711 }
2712
2713 cout<<"DNSKEY algorithms supported by this installation of PowerDNS:"<<endl;
2714
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;
2721 cout<<endl;
2722 }
2723 return 0;
2724 }
2725
2726 reportAllTypes();
2727
2728 if (cmds.at(0) == "create-bind-db") {
2729 #ifdef HAVE_SQLITE3
2730 if(cmds.size() != 2) {
2731 cerr << "Syntax: pdnsutil create-bind-db FNAME"<<endl;
2732 return 0;
2733 }
2734 try {
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);
2740 }
2741 }
2742 catch(SSqlException& se) {
2743 throw PDNSException("Error creating database in BIND backend: "+se.txtReason());
2744 }
2745 return 0;
2746 #else
2747 cerr<<"bind-dnssec-db requires building PowerDNS with SQLite3"<<endl;
2748 return 1;
2749 #endif
2750 }
2751
2752 if (cmds.at(0) == "raw-lua-from-content") {
2753 if (cmds.size() < 3) {
2754 cerr<<"Usage: raw-lua-from-content TYPE CONTENT"<<endl;
2755 return 1;
2756 }
2757
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;
2763
2764 return 0;
2765 }
2766 else if (cmds.at(0) == "hash-password") {
2767 uint64_t workFactor = CredentialsHolder::s_defaultWorkFactor;
2768 if (cmds.size() > 1) {
2769 try {
2770 pdns::checked_stoi_into(workFactor, cmds.at(1));
2771 }
2772 catch (const std::exception& e) {
2773 cerr<<"Unable to parse the supplied work factor: "<<e.what()<<endl;
2774 return 1;
2775 }
2776 }
2777
2778 auto password = CredentialsHolder::readFromTerminal();
2779
2780 try {
2781 cout<<hashPassword(password.getString(), workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize)<<endl;
2782 return EXIT_SUCCESS;
2783 }
2784 catch (const std::exception& e) {
2785 cerr<<"Error while hashing the supplied password: "<<e.what()<<endl;
2786 return 1;
2787 }
2788 }
2789
2790 if(cmds[0] == "zonemd-verify-file") {
2791 if(cmds.size() < 3) {
2792 cerr<<"Syntax: pdnsutil zonemd-verify-file ZONE FILENAME"<<endl;
2793 return 1;
2794 }
2795 if(cmds[1]==".")
2796 cmds[1].clear();
2797
2798 auto ret = zonemdVerifyFile(DNSName(cmds[1]), cmds[2]);
2799 return ret;
2800 }
2801
2802 DNSSECKeeper dk;
2803
2804 if (cmds.at(0) == "test-schema") {
2805 if(cmds.size() != 2) {
2806 cerr << "Syntax: pdnsutil test-schema ZONE"<<endl;
2807 return 0;
2808 }
2809 return testSchema(dk, DNSName(cmds.at(1)));
2810 }
2811 if (cmds.at(0) == "rectify-zone") {
2812 if(cmds.size() < 2) {
2813 cerr << "Syntax: pdnsutil rectify-zone ZONE [ZONE..]"<<endl;
2814 return 0;
2815 }
2816 unsigned int exitCode = 0;
2817 for(unsigned int n = 1; n < cmds.size(); ++n)
2818 if (!rectifyZone(dk, DNSName(cmds.at(n))))
2819 exitCode = 1;
2820 return exitCode;
2821 }
2822 else if (cmds.at(0) == "rectify-all-zones") {
2823 bool quiet = (cmds.size() >= 2 && cmds.at(1) == "quiet");
2824 if (!rectifyAllZones(dk, quiet)) {
2825 return 1;
2826 }
2827 }
2828 else if (cmds.at(0) == "check-zone") {
2829 if(cmds.size() != 2) {
2830 cerr << "Syntax: pdnsutil check-zone ZONE"<<endl;
2831 return 0;
2832 }
2833 UeberBackend B("default");
2834 return checkZone(dk, B, DNSName(cmds.at(1)));
2835 }
2836 else if (cmds.at(0) == "bench-db") {
2837 dbBench(cmds.size() > 1 ? cmds.at(1) : "");
2838 }
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);
2842 }
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;
2846 return 0;
2847 }
2848 if (cmds.size() == 2)
2849 return listAllZones(cmds.at(1));
2850 return listAllZones();
2851 }
2852 else if (cmds.at(0) == "list-member-zones") {
2853 if (cmds.size() != 2) {
2854 cerr << "Syntax: pdnsutil list-member-zones CATALOG" << endl;
2855 return 0;
2856 }
2857 return listMemberZones(cmds.at(1));
2858 }
2859 else if (cmds.at(0) == "test-zone") {
2860 cerr << "Did you mean check-zone?"<<endl;
2861 return 0;
2862 }
2863 else if (cmds.at(0) == "test-all-zones") {
2864 cerr << "Did you mean check-all-zones?"<<endl;
2865 return 0;
2866 }
2867 #if 0
2868 else if(cmds.at(0) == "signing-server" )
2869 {
2870 signingServer();
2871 }
2872 else if(cmds.at(0) == "signing-secondary")
2873 {
2874 launchSigningService(0);
2875 }
2876 #endif
2877 else if (cmds.at(0) == "test-speed") {
2878 if(cmds.size() < 2) {
2879 cerr << "Syntax: pdnsutil test-speed numcores [signing-server]"<<endl;
2880 return 0;
2881 }
2882 testSpeed(DNSName(cmds.at(1)), (cmds.size() > 3) ? cmds.at(3) : "", pdns::checked_stoi<int>(cmds.at(2)));
2883 }
2884 else if (cmds.at(0) == "verify-crypto") {
2885 if(cmds.size() != 2) {
2886 cerr << "Syntax: pdnsutil verify-crypto FILE"<<endl;
2887 return 0;
2888 }
2889 verifyCrypto(cmds.at(1));
2890 }
2891 else if (cmds.at(0) == "show-zone") {
2892 if(cmds.size() != 2) {
2893 cerr << "Syntax: pdnsutil show-zone ZONE"<<endl;
2894 return 0;
2895 }
2896 if (!showZone(dk, DNSName(cmds.at(1))))
2897 return 1;
2898 }
2899 else if (cmds.at(0) == "export-zone-ds") {
2900 if(cmds.size() != 2) {
2901 cerr << "Syntax: pdnsutil export-zone-ds ZONE"<<endl;
2902 return 0;
2903 }
2904 if (!showZone(dk, DNSName(cmds.at(1)), true))
2905 return 1;
2906 }
2907 else if (cmds.at(0) == "disable-dnssec") {
2908 if(cmds.size() != 2) {
2909 cerr << "Syntax: pdnsutil disable-dnssec ZONE"<<endl;
2910 return 0;
2911 }
2912 DNSName zone(cmds.at(1));
2913 if(!disableDNSSECOnZone(dk, zone)) {
2914 cerr << "Cannot disable DNSSEC on " << zone << endl;
2915 return 1;
2916 }
2917 }
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;
2921 return 0;
2922 }
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
2925 if(!id)
2926 {
2927 cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
2928 return 1;
2929 }
2930 try {
2931 dk.getKeyById(zone, id);
2932 } catch (std::exception& e) {
2933 cerr<<e.what()<<endl;
2934 return 1;
2935 }
2936 if (!dk.activateKey(zone, id)) {
2937 cerr<<"Activation of key failed"<<endl;
2938 return 1;
2939 }
2940 return 0;
2941 }
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;
2945 return 0;
2946 }
2947 DNSName zone(cmds.at(1));
2948 auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
2949 if(!id)
2950 {
2951 cerr<<"Invalid KEY-ID"<<endl;
2952 return 1;
2953 }
2954 try {
2955 dk.getKeyById(zone, id);
2956 } catch (std::exception& e) {
2957 cerr<<e.what()<<endl;
2958 return 1;
2959 }
2960 if (!dk.deactivateKey(zone, id)) {
2961 cerr<<"Deactivation of key failed"<<endl;
2962 return 1;
2963 }
2964 return 0;
2965 }
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;
2969 return 0;
2970 }
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
2973 if(!id)
2974 {
2975 cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
2976 return 1;
2977 }
2978 try {
2979 dk.getKeyById(zone, id);
2980 } catch (std::exception& e) {
2981 cerr<<e.what()<<endl;
2982 return 1;
2983 }
2984 if (!dk.publishKey(zone, id)) {
2985 cerr<<"Publishing of key failed"<<endl;
2986 return 1;
2987 }
2988 return 0;
2989 }
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;
2993 return 0;
2994 }
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
2997 if(!id)
2998 {
2999 cerr << "Invalid KEY-ID '" << cmds.at(2) << "'" << endl;
3000 return 1;
3001 }
3002 try {
3003 dk.getKeyById(zone, id);
3004 } catch (std::exception& e) {
3005 cerr<<e.what()<<endl;
3006 return 1;
3007 }
3008 if (!dk.unpublishKey(zone, id)) {
3009 cerr<<"Unpublishing of key failed"<<endl;
3010 return 1;
3011 }
3012 return 0;
3013 }
3014
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)
3019 cerr << "|ed25519";
3020 #endif
3021 #if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
3022 cerr << "|ed448";
3023 #endif
3024 cerr << "]"<<endl;
3025 cerr << endl;
3026 cerr << "If zsk|ksk is omitted, add-zone-key makes a key with flags 256 (a 'ZSK')."<<endl;
3027 return 0;
3028 }
3029 DNSName zone(cmds.at(1));
3030
3031 UeberBackend B("default");
3032 DomainInfo di;
3033
3034 if (!B.getDomainInfo(zone, di)){
3035 cerr << "No such zone in the database" << endl;
3036 return 0;
3037 }
3038
3039 // need to get algorithm, bits & ksk or zsk from commandline
3040 bool keyOrZone=false;
3041 int tmp_algo=0;
3042 int bits=0;
3043 int algorithm=DNSSECKeeper::ECDSA256;
3044 bool active=false;
3045 bool published=true;
3046 for(unsigned int n=2; n < cmds.size(); ++n) {
3047 if (pdns_iequals(cmds.at(n), "zsk"))
3048 keyOrZone = false;
3049 else if (pdns_iequals(cmds.at(n), "ksk"))
3050 keyOrZone = true;
3051 else if ((tmp_algo = DNSSECKeeper::shorthand2algorithm(cmds.at(n))) > 0) {
3052 algorithm = tmp_algo;
3053 }
3054 else if (pdns_iequals(cmds.at(n), "active")) {
3055 active=true;
3056 }
3057 else if (pdns_iequals(cmds.at(n), "inactive") || pdns_iequals(cmds.at(n), "passive")) { // 'passive' eventually needs to be removed
3058 active=false;
3059 }
3060 else if (pdns_iequals(cmds.at(n), "published")) {
3061 published = true;
3062 }
3063 else if (pdns_iequals(cmds.at(n), "unpublished")) {
3064 published = false;
3065 }
3066 else if (pdns::checked_stoi<int>(cmds.at(n)) != 0) {
3067 pdns::checked_stoi_into(bits, cmds.at(n));
3068 }
3069 else {
3070 cerr << "Unknown algorithm, key flag or size '" << cmds.at(n) << "'" << endl;
3071 return EXIT_FAILURE;
3072 }
3073 }
3074 int64_t id{-1};
3075 if (!dk.addKey(zone, keyOrZone, algorithm, id, bits, active, published)) {
3076 cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
3077 return 1;
3078 } else {
3079 cerr<<"Added a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<", active="<<active<<endl;
3080 if (bits)
3081 cerr<<"Requested specific key size of "<<bits<<" bits"<<endl;
3082 if (id == -1) {
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;
3086 } else {
3087 cout<<std::to_string(id)<<endl;
3088 }
3089 }
3090 }
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;
3094 return 0;
3095 }
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;
3100 return 1;
3101 }
3102 return 0;
3103 }
3104 else if (cmds.at(0) == "delete-zone") {
3105 if(cmds.size() != 2) {
3106 cerr<<"Syntax: pdnsutil delete-zone ZONE"<<endl;
3107 return 0;
3108 }
3109 return deleteZone(DNSName(cmds.at(1)));
3110 }
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;
3114 return 0;
3115 }
3116 return createZone(DNSName(cmds.at(1)), cmds.size() > 2 ? DNSName(cmds.at(2)) : DNSName());
3117 }
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;
3121 return 0;
3122 }
3123 return createSecondaryZone(cmds);
3124 }
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;
3128 return 0;
3129 }
3130 return changeSecondaryZonePrimary(cmds);
3131 }
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;
3135 return 0;
3136 }
3137 return addOrReplaceRecord(true, cmds);
3138 }
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;
3142 return 0;
3143 }
3144 exit(addAutoPrimary(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : ""));
3145 }
3146 else if (cmds.at(0) == "remove-autoprimary") {
3147 if(cmds.size() < 3) {
3148 cerr << "Syntax: pdnsutil remove-autoprimary IP NAMESERVER" << endl;
3149 return 0;
3150 }
3151 exit(removeAutoPrimary(cmds.at(1), cmds.at(2)));
3152 }
3153 else if (cmds.at(0) == "list-autoprimaries") {
3154 exit(listAutoPrimaries());
3155 }
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;
3159 return 0;
3160 }
3161 return addOrReplaceRecord(false , cmds);
3162 }
3163 else if (cmds.at(0) == "delete-rrset") {
3164 if(cmds.size() != 4) {
3165 cerr<<"Syntax: pdnsutil delete-rrset ZONE name type"<<endl;
3166 return 0;
3167 }
3168 return deleteRRSet(cmds.at(1), cmds.at(2), cmds.at(3));
3169 }
3170 else if (cmds.at(0) == "list-zone") {
3171 if(cmds.size() != 2) {
3172 cerr<<"Syntax: pdnsutil list-zone ZONE"<<endl;
3173 return 0;
3174 }
3175 if (cmds.at(1) == ".")
3176 cmds.at(1).clear();
3177
3178 return listZone(DNSName(cmds.at(1)));
3179 }
3180 else if (cmds.at(0) == "edit-zone") {
3181 if(cmds.size() != 2) {
3182 cerr<<"Syntax: pdnsutil edit-zone ZONE"<<endl;
3183 return 0;
3184 }
3185 if (cmds.at(1) == ".")
3186 cmds.at(1).clear();
3187
3188 PDNSColors col(g_vm.count("no-colors"));
3189 return editZone(DNSName(cmds.at(1)), col);
3190 }
3191 else if (cmds.at(0) == "clear-zone") {
3192 if(cmds.size() != 2) {
3193 cerr<<"Syntax: pdnsutil clear-zone ZONE"<<endl;
3194 return 0;
3195 }
3196 if (cmds.at(1) == ".")
3197 cmds.at(1).clear();
3198
3199 return clearZone(DNSName(cmds.at(1)));
3200 }
3201 else if (cmds.at(0) == "list-keys") {
3202 if(cmds.size() > 2) {
3203 cerr<<"Syntax: pdnsutil list-keys [ZONE]"<<endl;
3204 return 0;
3205 }
3206 string zname;
3207 if (cmds.size() == 2) {
3208 zname = cmds.at(1);
3209 }
3210 return listKeys(zname, dk);
3211 }
3212 else if (cmds.at(0) == "load-zone") {
3213 if(cmds.size() < 3) {
3214 cerr<<"Syntax: pdnsutil load-zone ZONE FILENAME [ZONE FILENAME] .."<<endl;
3215 return 0;
3216 }
3217 if (cmds.at(1) == ".")
3218 cmds.at(1).clear();
3219
3220 for(size_t n=1; n + 2 <= cmds.size(); n+=2) {
3221 auto ret = loadZone(DNSName(cmds.at(n)), cmds.at(n + 1));
3222 if (ret) exit(ret);
3223 }
3224 return 0;
3225 }
3226 else if (cmds.at(0) == "secure-zone") {
3227 if(cmds.size() < 2) {
3228 cerr << "Syntax: pdnsutil secure-zone ZONE"<<endl;
3229 return 0;
3230 }
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);
3238 } else {
3239 zoneErrors++;
3240 }
3241 dk.commitTransaction();
3242 }
3243
3244 for(const auto& zone : mustRectify)
3245 rectifyZone(dk, zone);
3246
3247 if (zoneErrors) {
3248 return 1;
3249 }
3250 return 0;
3251 }
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;
3255 return 0;
3256 }
3257
3258 UeberBackend B("default");
3259
3260 vector<DomainInfo> domainInfo;
3261 B.getAllDomains(&domainInfo, false, false);
3262
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)) {
3268 zonesSecured++;
3269 if (cmds.size() == 2) {
3270 if (!increaseSerial(di.zone, dk))
3271 continue;
3272 } else
3273 continue;
3274 }
3275 zoneErrors++;
3276 }
3277 }
3278
3279 cout<<"Secured: "<<zonesSecured<<" zones. Errors: "<<zoneErrors<<endl;
3280
3281 if (zoneErrors) {
3282 return 1;
3283 }
3284 return 0;
3285 }
3286 else if (cmds.at(0) == "set-kind") {
3287 if(cmds.size() != 3) {
3288 cerr<<"Syntax: pdnsutil set-kind ZONE KIND"<<endl;
3289 return 0;
3290 }
3291 DNSName zone(cmds.at(1));
3292 auto kind = DomainInfo::stringToKind(cmds.at(2));
3293 return setZoneKind(zone, kind);
3294 }
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;
3299 }
3300
3301 // Verify json
3302 if (!cmds.at(2).empty()) {
3303 std::string err;
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;
3308 }
3309 }
3310
3311 DNSName zone(cmds.at(1));
3312
3313 return setZoneOptionsJson(zone, cmds.at(2));
3314 }
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;
3319 }
3320
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;
3324 }
3325
3326 DNSName zone(cmds.at(1));
3327 set<string> values;
3328 for (unsigned int n = 4; n < cmds.size(); ++n) {
3329 if (!cmds.at(n).empty()) {
3330 values.insert(cmds.at(n));
3331 }
3332 }
3333
3334 return setZoneOption(zone, cmds.at(2), cmds.at(3), values);
3335 }
3336 else if (cmds.at(0) == "set-catalog") {
3337 if (cmds.size() != 3) {
3338 cerr << "Syntax: pdnsutil set-catalog ZONE CATALOG" << endl;
3339 return 0;
3340 }
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));
3345 }
3346 return setZoneCatalog(zone, catalog);
3347 }
3348 else if (cmds.at(0) == "set-account") {
3349 if(cmds.size() != 3) {
3350 cerr<<"Syntax: pdnsutil set-account ZONE ACCOUNT"<<endl;
3351 return 0;
3352 }
3353 DNSName zone(cmds.at(1));
3354 return setZoneAccount(zone, cmds.at(2));
3355 }
3356 else if (cmds.at(0) == "set-nsec3") {
3357 if(cmds.size() < 2) {
3358 cerr<<"Syntax: pdnsutil set-nsec3 ZONE 'params' [narrow]"<<endl;
3359 return 0;
3360 }
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);
3364
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;
3368 return 1;
3369 }
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;
3373 }
3374 if (! dk.setNSEC3PARAM(zone, ns3pr, narrow)) {
3375 cerr<<"Cannot set NSEC3 param for " << zone << endl;
3376 return 1;
3377 }
3378
3379 if (!ns3pr.d_flags)
3380 cerr<<"NSEC3 set, ";
3381 else
3382 cerr<<"NSEC3 (opt-out) set, ";
3383
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;
3386 else
3387 cerr<<"Done, please secure and rectify your zone (or reload it if you are using the bindbackend)"<<endl;
3388
3389 return 0;
3390 }
3391 else if (cmds.at(0) == "set-presigned") {
3392 if(cmds.size() < 2) {
3393 cerr<<"Syntax: pdnsutil set-presigned ZONE"<<endl;
3394 return 0;
3395 }
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;
3398 return 1;
3399 }
3400 return 0;
3401 }
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;
3405 return 0;
3406 }
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;
3409 return 1;
3410 }
3411 return 0;
3412 }
3413 else if (cmds.at(0) == "set-publish-cds") {
3414 if(cmds.size() < 2) {
3415 cerr<<"Syntax: pdnsutil set-publish-cds ZONE [DIGESTALGOS]"<<endl;
3416 return 0;
3417 }
3418
3419 // If DIGESTALGOS is unset
3420 if(cmds.size() == 2)
3421 cmds.push_back("2");
3422
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;
3425 return 1;
3426 }
3427 return 0;
3428 }
3429 else if (cmds.at(0) == "unset-presigned") {
3430 if(cmds.size() < 2) {
3431 cerr<<"Syntax: pdnsutil unset-presigned ZONE"<<endl;
3432 return 0;
3433 }
3434 if (!dk.unsetPresigned(DNSName(cmds.at(1)))) {
3435 cerr << "Could not unset presigned on for " << cmds.at(1) << endl;
3436 return 1;
3437 }
3438 return 0;
3439 }
3440 else if (cmds.at(0) == "unset-publish-cdnskey") {
3441 if(cmds.size() < 2) {
3442 cerr<<"Syntax: pdnsutil unset-publish-cdnskey ZONE"<<endl;
3443 return 0;
3444 }
3445 if (!dk.unsetPublishCDNSKEY(DNSName(cmds.at(1)))) {
3446 cerr << "Could not unset publishing for CDNSKEY records for " << cmds.at(1) << endl;
3447 return 1;
3448 }
3449 return 0;
3450 }
3451 else if (cmds.at(0) == "unset-publish-cds") {
3452 if(cmds.size() < 2) {
3453 cerr<<"Syntax: pdnsutil unset-publish-cds ZONE"<<endl;
3454 return 0;
3455 }
3456 if (!dk.unsetPublishCDS(DNSName(cmds.at(1)))) {
3457 cerr << "Could not unset publishing for CDS records for " << cmds.at(1) << endl;
3458 return 1;
3459 }
3460 return 0;
3461 }
3462 else if(cmds.at(0) == "hash-password") {
3463 if (cmds.size() < 2) {
3464 cerr<<"Syntax: pdnsutil hash-password PASSWORD"<<endl;
3465 return 0;
3466 }
3467 cout<<hashPassword(cmds.at(1))<<endl;
3468 return 0;
3469 }
3470 else if (cmds.at(0) == "hash-zone-record") {
3471 if(cmds.size() < 3) {
3472 cerr<<"Syntax: pdnsutil hash-zone-record ZONE RNAME"<<endl;
3473 return 0;
3474 }
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;
3481 return 0;
3482 }
3483 if(narrow) {
3484 cerr<<"The '"<<zone<<"' zone uses narrow NSEC3, but calculating hash anyhow"<<endl;
3485 }
3486
3487 cout<<toBase32Hex(hashQNameWithSalt(ns3pr, record))<<endl;
3488 }
3489 else if (cmds.at(0) == "unset-nsec3") {
3490 if(cmds.size() < 2) {
3491 cerr<<"Syntax: pdnsutil unset-nsec3 ZONE"<<endl;
3492 return 0;
3493 }
3494 if (!dk.unsetNSEC3PARAM(DNSName(cmds.at(1)))) {
3495 cerr << "Cannot unset NSEC3 param for " << cmds.at(1) << endl;
3496 return 1;
3497 }
3498 cerr<<"Done, please rectify your zone if your backend needs it (or reload it if you are using the bindbackend)"<<endl;
3499
3500 return 0;
3501 }
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;
3505 return 1;
3506 }
3507
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;
3512 }
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;
3516 return 1;
3517 }
3518
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);
3523 }
3524 else if (cmds.at(0) == "increase-serial") {
3525 if (cmds.size() < 2) {
3526 cerr << "Syntax: pdnsutil increase-serial ZONE" << endl;
3527 return 1;
3528 }
3529 return increaseSerial(DNSName(cmds.at(1)), dk);
3530 }
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;
3534 return 1;
3535 }
3536
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));
3540
3541 errno = 0;
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);
3546 }
3547
3548 DNSKEYRecordContent drc;
3549 shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *fp, filename)};
3550 if (!key) {
3551 cerr << "Could not convert key from PEM to internal format" << endl;
3552 return 1;
3553 }
3554
3555 DNSSECPrivateKey dpk;
3556
3557 uint8_t algo = 0;
3558 pdns::checked_stoi_into(algo, cmds.at(3));
3559 if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
3560 algo = DNSSECKeeper::RSASHA1;
3561 }
3562
3563 cerr << std::to_string(algo) << endl;
3564
3565 uint16_t flags = 0;
3566 if (cmds.size() > 4) {
3567 if (pdns_iequals(cmds.at(4), "ZSK")) {
3568 flags = 256;
3569 }
3570 else if (pdns_iequals(cmds.at(4), "KSK")) {
3571 flags = 257;
3572 }
3573 else {
3574 cerr << "Unknown key flag '" << cmds.at(4) << "'" << endl;
3575 return 1;
3576 }
3577 }
3578 else {
3579 flags = 257; // ksk
3580 }
3581 dpk.setKey(key, flags, algo);
3582
3583 int64_t id{-1};
3584 if (!dk.addKey(DNSName(zone), dpk, id)) {
3585 cerr << "Adding key failed, perhaps DNSSEC not enabled in configuration?" << endl;
3586 return 1;
3587 }
3588
3589 if (id == -1) {
3590 cerr << std::to_string(id) << "Key was added, but backend does not support returning of key id" << endl;
3591 }
3592 else if (id < -1) {
3593 cerr << std::to_string(id) << "Key was added, but there was a failure while returning the key id" << endl;
3594 }
3595 else {
3596 cout << std::to_string(id) << endl;
3597 }
3598 }
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;
3602 return 1;
3603 }
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()));
3608
3609 uint16_t flags = 257;
3610 bool active=true;
3611 bool published=true;
3612
3613 for(unsigned int n = 3; n < cmds.size(); ++n) {
3614 if (pdns_iequals(cmds.at(n), "ZSK"))
3615 flags = 256;
3616 else if (pdns_iequals(cmds.at(n), "KSK"))
3617 flags = 257;
3618 else if (pdns_iequals(cmds.at(n), "active"))
3619 active = true;
3620 else if (pdns_iequals(cmds.at(n), "passive") || pdns_iequals(cmds.at(n), "inactive")) // passive eventually needs to be removed
3621 active = false;
3622 else if (pdns_iequals(cmds.at(n), "published"))
3623 published = true;
3624 else if (pdns_iequals(cmds.at(n), "unpublished"))
3625 published = false;
3626 else {
3627 cerr << "Unknown key flag '" << cmds.at(n) << "'" << endl;
3628 return 1;
3629 }
3630 }
3631
3632 DNSSECPrivateKey dpk;
3633 uint8_t algo = key->getAlgorithm();
3634 if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
3635 algo = DNSSECKeeper::RSASHA1;
3636 }
3637 dpk.setKey(key, flags, algo);
3638
3639 int64_t id{-1};
3640 if (!dk.addKey(DNSName(zone), dpk, id, active, published)) {
3641 cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
3642 return 1;
3643 }
3644 if (id == -1) {
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;
3648 } else {
3649 cout<<std::to_string(id)<<endl;
3650 }
3651 }
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;
3655 return 1;
3656 }
3657
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;
3662 }
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)
3667 cerr << "|ed25519";
3668 #endif
3669 #if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
3670 cerr << "|ed448";
3671 #endif
3672 cerr << "] [bits]"<<endl;
3673 return 0;
3674 }
3675 // need to get algorithm, bits & ksk or zsk from commandline
3676 bool keyOrZone=false;
3677 int tmp_algo=0;
3678 int bits=0;
3679 int algorithm=DNSSECKeeper::ECDSA256;
3680 for(unsigned int n=1; n < cmds.size(); ++n) {
3681 if (pdns_iequals(cmds.at(n), "zsk"))
3682 keyOrZone = false;
3683 else if (pdns_iequals(cmds.at(n), "ksk"))
3684 keyOrZone = true;
3685 else if ((tmp_algo = DNSSECKeeper::shorthand2algorithm(cmds.at(n))) > 0) {
3686 algorithm = tmp_algo;
3687 }
3688 else if (pdns::checked_stoi<int>(cmds.at(n)) != 0)
3689 pdns::checked_stoi_into(bits, cmds.at(n));
3690 else {
3691 cerr << "Unknown algorithm, key flag or size '" << cmds.at(n) << "'" << endl;
3692 return 0;
3693 }
3694 }
3695 cerr<<"Generating a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<endl;
3696 if(bits)
3697 cerr<<"Requesting specific key size of "<<bits<<" bits"<<endl;
3698
3699 shared_ptr<DNSCryptoKeyEngine> dpk(DNSCryptoKeyEngine::make(algorithm));
3700 if(!bits) {
3701 if(algorithm <= 10)
3702 bits = keyOrZone ? 2048 : 1024;
3703 else {
3704 if(algorithm == DNSSECKeeper::ECCGOST || algorithm == DNSSECKeeper::ECDSA256 || algorithm == DNSSECKeeper::ED25519)
3705 bits = 256;
3706 else if(algorithm == DNSSECKeeper::ECDSA384)
3707 bits = 384;
3708 else if(algorithm == DNSSECKeeper::ED448)
3709 bits = 456;
3710 else {
3711 throw runtime_error("Can not guess key size for algorithm "+std::to_string(algorithm));
3712 }
3713 }
3714 }
3715 dpk->create(bits);
3716 DNSSECPrivateKey dspk;
3717 dspk.setKey(dpk, keyOrZone ? 257 : 256, algorithm);
3718
3719 // print key to stdout
3720 cout << "Flags: " << dspk.getFlags() << endl <<
3721 dspk.getKey()->convertToISC() << endl;
3722 }
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;
3727 return 0;
3728 }
3729 DNSName name(cmds.at(1));
3730 DNSName algo(cmds.at(2));
3731 string key;
3732 try {
3733 key = makeTSIGKey(algo);
3734 } catch(const PDNSException& e) {
3735 cerr << "Could not create new TSIG key " << name << " " << algo << ": "<< e.reason << endl;
3736 return 1;
3737 }
3738
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;
3742 } else {
3743 cerr << "Failure storing new TSIG key " << name << " " << algo << " " << key << endl;
3744 return 1;
3745 }
3746 return 0;
3747 }
3748 else if (cmds.at(0) == "import-tsig-key") {
3749 if (cmds.size() < 4) {
3750 cerr << "Syntax: " << cmds.at(0) << " name algorithm key" << endl;
3751 return 0;
3752 }
3753 DNSName name(cmds.at(1));
3754 string algo = cmds.at(2);
3755 string key = cmds.at(3);
3756
3757 UeberBackend B("default");
3758 if (B.setTSIGKey(name, DNSName(algo), key)) {
3759 cout << "Imported TSIG key " << name << " " << algo << endl;
3760 }
3761 else {
3762 cerr << "Failure importing TSIG key " << name << " " << algo << endl;
3763 return 1;
3764 }
3765 return 0;
3766 }
3767 else if (cmds.at(0) == "delete-tsig-key") {
3768 if (cmds.size() < 2) {
3769 cerr << "Syntax: " << cmds.at(0) << " name" << endl;
3770 return 0;
3771 }
3772 DNSName name(cmds.at(1));
3773
3774 UeberBackend B("default");
3775 if (B.deleteTSIGKey(name)) {
3776 cout << "Deleted TSIG key " << name << endl;
3777 }
3778 else {
3779 cerr << "Failure deleting TSIG key " << name << endl;
3780 return 1;
3781 }
3782 return 0;
3783 }
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;
3790 }
3791 }
3792 return 0;
3793 }
3794 else if (cmds.at(0) == "activate-tsig-key") {
3795 string metaKey;
3796 if (cmds.size() < 4) {
3797 cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary}" << endl;
3798 return 0;
3799 }
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";
3806 else {
3807 cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
3808 return 1;
3809 }
3810 UeberBackend B("default");
3811 DomainInfo di;
3812 if (!B.getDomainInfo(zname, di)) {
3813 cerr << "Zone '" << zname << "' does not exist" << endl;
3814 return 1;
3815 }
3816 std::vector<std::string> meta;
3817 if (!B.getDomainMetadata(zname, metaKey, meta)) {
3818 cerr << "Failure enabling TSIG key " << name << " for " << zname << endl;
3819 return 1;
3820 }
3821 bool found = false;
3822 for (const std::string& tmpname : meta) {
3823 if (tmpname == name) {
3824 found = true;
3825 break;
3826 }
3827 }
3828 if (!found)
3829 meta.push_back(name);
3830 if (B.setDomainMetadata(zname, metaKey, meta)) {
3831 cout << "Enabled TSIG key " << name << " for " << zname << endl;
3832 }
3833 else {
3834 cerr << "Failure enabling TSIG key " << name << " for " << zname << endl;
3835 return 1;
3836 }
3837 return 0;
3838 }
3839 else if (cmds.at(0) == "deactivate-tsig-key") {
3840 string metaKey;
3841 if (cmds.size() < 4) {
3842 cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary|producer|consumer}" << endl;
3843 return 0;
3844 }
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";
3851 else {
3852 cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
3853 return 1;
3854 }
3855
3856 UeberBackend B("default");
3857 DomainInfo di;
3858 if (!B.getDomainInfo(zname, di)) {
3859 cerr << "Zone '" << zname << "' does not exist" << endl;
3860 return 1;
3861 }
3862 std::vector<std::string> meta;
3863 if (!B.getDomainMetadata(zname, metaKey, meta)) {
3864 cerr << "Failure disabling TSIG key " << name << " for " << zname << endl;
3865 return 1;
3866 }
3867 std::vector<std::string>::iterator iter = meta.begin();
3868 for (; iter != meta.end(); ++iter)
3869 if (*iter == name)
3870 break;
3871 if (iter != meta.end())
3872 meta.erase(iter);
3873 if (B.setDomainMetadata(zname, metaKey, meta)) {
3874 cout << "Disabled TSIG key " << name << " for " << zname << endl;
3875 }
3876 else {
3877 cerr << "Failure disabling TSIG key " << name << " for " << zname << endl;
3878 return 1;
3879 }
3880 return 0;
3881 }
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;
3886 return 1;
3887 }
3888 DNSName zone(cmds.at(1));
3889 vector<string> keys;
3890
3891 DomainInfo di;
3892 if (!B.getDomainInfo(zone, di)) {
3893 cerr << "Invalid zone '" << zone << "'" << endl;
3894 return 1;
3895 }
3896
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;
3902 meta.clear();
3903 if (B.getDomainMetadata(zone, kind, meta)) {
3904 cout << kind << " = " << boost::join(meta, ", ") << endl;
3905 }
3906 }
3907 } else {
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;
3913 }
3914 }
3915 return 0;
3916 }
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;
3920 return 1;
3921 }
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",
3926 "PUBLISH-CDS"};
3927 bool clobber = true;
3928 if (cmds.at(0) == "add-meta") {
3929 clobber = false;
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;
3932 return 1;
3933 }
3934 }
3935 vector<string> meta(cmds.begin() + 3, cmds.end());
3936 return addOrSetMeta(zone, kind, meta, clobber);
3937 }
3938 else if (cmds.at(0) == "hsm") {
3939 #ifdef HAVE_P11KIT1
3940 UeberBackend B("default");
3941 if (cmds.size() < 2) {
3942 cerr << "Missing sub-command for pdnsutil hsm"<< std::endl;
3943 return 0;
3944 }
3945 else if (cmds.at(1) == "assign") {
3946 DNSCryptoKeyEngine::storvector_t storvect;
3947 DomainInfo di;
3948 std::vector<DNSBackend::KeyData> keys;
3949
3950 if (cmds.size() < 9) {
3951 std::cout << "Usage: pdnsutil hsm assign ZONE ALGORITHM {ksk|zsk} MODULE TOKEN PIN LABEL (PUBLABEL)" << std::endl;
3952 return 1;
3953 }
3954
3955 DNSName zone(cmds.at(2));
3956
3957 // verify zone
3958 if (!B.getDomainInfo(zone, di)) {
3959 cerr << "Unable to assign module to unknown zone '" << zone << "'" << std::endl;
3960 return 1;
3961 }
3962
3963 int algorithm = DNSSECKeeper::shorthand2algorithm(cmds.at(3));
3964 if (algorithm<0) {
3965 cerr << "Unable to use unknown algorithm '" << cmds.at(3) << "'" << std::endl;
3966 return 1;
3967 }
3968
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);
3974 string pub_label;
3975 if (cmds.size() > 9)
3976 pub_label = cmds.at(9);
3977 else
3978 pub_label = label;
3979
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;
3988
3989 DNSKEYRecordContent drc;
3990
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;
3994 return 1;
3995 }
3996 DNSSECPrivateKey dpk;
3997 dpk.setKey(dke, keyOrZone ? 257 : 256);
3998
3999 // make sure this key isn't being reused.
4000 B.getDomainKeys(zone, keys);
4001
4002 int64_t id{-1};
4003 for(DNSBackend::KeyData& kd : keys) {
4004 if (kd.content == iscString.str()) {
4005 // it's this one, I guess...
4006 id = kd.id;
4007 break;
4008 }
4009 }
4010
4011 if (id > -1) {
4012 cerr << "You have already assigned this key with ID=" << id << std::endl;
4013 return 1;
4014 }
4015
4016 if (!dk.addKey(zone, dpk, id)) {
4017 cerr << "Unable to assign module slot to zone" << std::endl;
4018 return 1;
4019 }
4020
4021 cerr << "Module " << module << " slot " << slot << " assigned to " << zone << " with key id " << id << endl;
4022
4023 return 0;
4024 }
4025 else if (cmds.at(1) == "create-key") {
4026
4027 if (cmds.size() < 4) {
4028 cerr << "Usage: pdnsutil hsm create-key ZONE KEY-ID [BITS]" << endl;
4029 return 1;
4030 }
4031 DomainInfo di;
4032 DNSName zone(cmds.at(2));
4033 unsigned int id;
4034 int bits = 2048;
4035 // verify zone
4036 if (!B.getDomainInfo(zone, di)) {
4037 cerr << "Unable to create key for unknown zone '" << zone << "'" << std::endl;
4038 return 1;
4039 }
4040
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;
4045 return 1;
4046 }
4047
4048 std::unique_ptr<DNSCryptoKeyEngine> dke = nullptr;
4049 // lookup correct key
4050 for(DNSBackend::KeyData &kd : keys) {
4051 if (kd.id == id) {
4052 // found our key.
4053 DNSKEYRecordContent dkrc;
4054 dke = DNSCryptoKeyEngine::makeFromISCString(dkrc, kd.content);
4055 }
4056 }
4057
4058 if (!dke) {
4059 cerr << "Could not find key with ID " << id << endl;
4060 return 1;
4061 }
4062 if (cmds.size() > 4) {
4063 pdns::checked_stoi_into(bits, cmds.at(4));
4064 }
4065 if (bits < 1) {
4066 cerr << "Invalid bit size " << bits << "given, must be positive integer";
4067 return 1;
4068 }
4069 try {
4070 dke->create(bits);
4071 } catch (PDNSException& e) {
4072 cerr << e.reason << endl;
4073 return 1;
4074 }
4075
4076 cerr << "Key of size " << dke->getBits() << " created" << std::endl;
4077 return 0;
4078 }
4079 #else
4080 cerr<<"PKCS#11 support not enabled"<<endl;
4081 return 1;
4082 #endif
4083 }
4084 else if (cmds.at(0) == "b2b-migrate") {
4085 if (cmds.size() < 3) {
4086 cerr << "Usage: b2b-migrate OLD NEW" << endl;
4087 return 1;
4088 }
4089
4090 if (cmds.at(1) == cmds.at(2)) {
4091 cerr << "Error: b2b-migrate OLD NEW: OLD cannot be the same as NEW" << endl;
4092 return 1;
4093 }
4094
4095 unique_ptr<DNSBackend> src{nullptr};
4096 unique_ptr<DNSBackend> tgt{nullptr};
4097
4098 for (auto& backend : BackendMakers().all()) {
4099 if (backend->getPrefix() == cmds.at(1)) {
4100 src = std::move(backend);
4101 }
4102 else if (backend->getPrefix() == cmds.at(2)) {
4103 tgt = std::move(backend);
4104 }
4105 }
4106
4107 if (src == nullptr) {
4108 cerr << "Unknown source backend '" << cmds.at(1) << "'" << endl;
4109 return 1;
4110 }
4111 if (tgt == nullptr) {
4112 cerr << "Unknown target backend '" << cmds.at(2) << "'" << endl;
4113 return 1;
4114 }
4115
4116 cout<<"Moving zone(s) from "<<src->getPrefix()<<" to "<<tgt->getPrefix()<<endl;
4117
4118 vector<DomainInfo> domains;
4119
4120 tgt->getAllDomains(&domains, false, true);
4121 if (!domains.empty())
4122 throw PDNSException("Target backend has zone(s), please clean it first");
4123
4124 src->getAllDomains(&domains, false, true);
4125 // iterate zones
4126 for(const DomainInfo& di: domains) {
4127 size_t nr,nc,nm,nk;
4128 DomainInfo di_new;
4129 DNSResourceRecord rr;
4130 cout<<"Processing '"<<di.zone<<"'"<<endl;
4131 // create zone
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");
4135 // move records
4136 if (!src->list(di.zone, di.id, true)) throw PDNSException("Failed to list records");
4137 nr=0;
4138
4139 tgt->startTransaction(di.zone, di_new.id);
4140
4141 while(src->get(rr)) {
4142 rr.domain_id = di_new.id;
4143 if (!tgt->feedRecord(rr, DNSName())) throw PDNSException("Failed to feed record");
4144 nr++;
4145 }
4146
4147 // move comments
4148 nc=0;
4149 if (src->listComments(di.id)) {
4150 Comment c;
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");
4155 }
4156 nc++;
4157 }
4158 }
4159 // move metadata
4160 nm=0;
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");
4166 nm++;
4167 }
4168 }
4169 // move keys
4170 nk=0;
4171 // temp var for KeyID
4172 int64_t 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);
4177 nk++;
4178 }
4179 }
4180 tgt->commitTransaction();
4181 cout<<"Moved "<<nr<<" record(s), "<<nc<<" comment(s), "<<nm<<" metadata(s) and "<<nk<<" cryptokey(s)"<<endl;
4182 }
4183
4184 int ntk=0;
4185 // move tsig keys
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");
4190 ntk++;
4191 }
4192 }
4193 cout<<"Moved "<<ntk<<" TSIG key(s)"<<endl;
4194
4195 cout<<"Remember to drop the old backend and run rectify-all-zones"<<endl;
4196
4197 return 0;
4198 }
4199 else if (cmds.at(0) == "backend-cmd") {
4200 if (cmds.size() < 3) {
4201 cerr<<"Usage: backend-cmd BACKEND CMD [CMD..]"<<endl;
4202 return 1;
4203 }
4204
4205 std::unique_ptr<DNSBackend> matchingBackend{nullptr};
4206
4207 for (auto& backend : BackendMakers().all()) {
4208 if (backend->getPrefix() == cmds.at(1)) {
4209 matchingBackend = std::move(backend);
4210 }
4211 }
4212
4213 if (matchingBackend == nullptr) {
4214 cerr << "Unknown backend '" << cmds.at(1) << "'" << endl;
4215 return 1;
4216 }
4217
4218 for (auto i = next(begin(cmds), 2); i != end(cmds); ++i) {
4219 cerr << "== " << *i << endl;
4220 cout << matchingBackend->directBackendCmd(*i);
4221 }
4222
4223 return 0;
4224 }
4225 else {
4226 cerr << "Unknown command '" << cmds.at(0) << "'" << endl;
4227 return 1;
4228 }
4229 return 0;
4230 }
4231 catch(PDNSException& ae) {
4232 cerr<<"Error: "<<ae.reason<<endl;
4233 return 1;
4234 }
4235 catch(std::exception& e) {
4236 cerr<<"Error: "<<e.what()<<endl;
4237 return 1;
4238 }
4239 catch(...)
4240 {
4241 cerr<<"Caught an unknown exception"<<endl;
4242 return 1;
4243 }