root/trunk/pdns/pdns/pdnssec.cc @ 1893

Revision 1893, 16.8 KB (checked in by ahu, 2 years ago)

implement 'pdnssec set-presigned', allowing PowerDNSSEC to serve pre-signed zones. Rather experimental, but does appear to work

Line 
1#include "dnsseckeeper.hh"
2#include "dnssecinfra.hh"
3#include "statbag.hh"
4#include "base32.hh"
5#include "base64.hh"
6#include <boost/foreach.hpp>
7#include <boost/program_options.hpp>
8#include "dnsbackend.hh"
9#include "ueberbackend.hh"
10#include "arguments.hh"
11#include "packetcache.hh"
12
13StatBag S;
14PacketCache PC;
15
16using namespace boost;
17namespace po = boost::program_options;
18po::variables_map g_vm;
19
20string s_programname="pdns";
21
22ArgvMap &arg()
23{
24  static ArgvMap arg;
25  return arg;
26}
27
28string humanTime(time_t t)
29{
30  char ret[256];
31  struct tm tm;
32  localtime_r(&t, &tm);
33  strftime(ret, sizeof(ret)-1, "%c", &tm);   // %h:%M %Y-%m-%d
34  return ret;
35}
36
37void loadMainConfig(const std::string& configdir)
38{
39  ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=configdir;
40 
41  ::arg().set("launch","Which backends to launch");
42  ::arg().set("dnssec","if we should do dnssec")="true";
43  ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")=g_vm["config-name"].as<string>();
44  ::arg().setCmd("help","Provide a helpful message");
45  //::arg().laxParse(argc,argv);
46
47  if(::arg().mustDo("help")) {
48    cerr<<"syntax:"<<endl<<endl;
49    cerr<<::arg().helpstring(::arg()["help"])<<endl;
50    exit(99);
51  }
52
53  if(::arg()["config-name"]!="") 
54    s_programname+="-"+::arg()["config-name"];
55
56  string configname=::arg()["config-dir"]+"/"+s_programname+".conf";
57  cleanSlashes(configname);
58 
59  ::arg().laxFile(configname.c_str());
60  ::arg().set("module-dir","Default directory for modules")=LIBDIR;
61  BackendMakers().launch(::arg()["launch"]); // vrooooom!
62  ::arg().laxFile(configname.c_str());   
63  //cerr<<"Backend: "<<::arg()["launch"]<<", '" << ::arg()["gmysql-dbname"] <<"'" <<endl;
64
65  S.declare("qsize-q","Number of questions waiting for database attention");
66   
67  S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
68  S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
69         
70  S.declare("query-cache-hit","Number of hits on the query cache");
71  S.declare("query-cache-miss","Number of misses on the query cache");
72  ::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
73  ::arg().set("recursor","If recursion is desired, IP address of a recursing nameserver")="no"; 
74  ::arg().set("recursive-cache-ttl","Seconds to store packets in the PacketCache")="10";
75  ::arg().set("cache-ttl","Seconds to store packets in the PacketCache")="20";             
76  ::arg().set("negquery-cache-ttl","Seconds to store packets in the PacketCache")="60";
77  ::arg().set("query-cache-ttl","Seconds to store packets in the PacketCache")="20";             
78  ::arg().set("soa-refresh-default","Default SOA refresh")="10800";
79  ::arg().set("soa-retry-default","Default SOA retry")="3600";
80  ::arg().set("soa-expire-default","Default SOA expire")="604800";
81    ::arg().setSwitch("query-logging","Hint backends that queries should be logged")="no";
82  ::arg().set("soa-minimum-ttl","Default SOA mininum ttl")="3600";   
83 
84  UeberBackend::go();
85}
86
87void rectifyZone(DNSSECKeeper& dk, const std::string& zone)
88{
89  UeberBackend* B = new UeberBackend("default");
90  SOAData sd;
91 
92  if(!B->getSOA(zone, sd)) {
93    cerr<<"No SOA known for '"<<zone<<"', is such a zone in the database?"<<endl;
94    return;
95  } 
96  sd.db->list(zone, sd.domain_id);
97  DNSResourceRecord rr;
98
99  set<string> qnames, nsset;
100 
101  while(sd.db->get(rr)) {
102    qnames.insert(rr.qname);
103    if(rr.qtype.getCode() == QType::NS && !pdns_iequals(rr.qname, zone)) 
104      nsset.insert(rr.qname);
105  }
106
107  NSEC3PARAMRecordContent ns3pr;
108  bool narrow;
109  bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
110  string hashed;
111  if(!haveNSEC3) 
112    cerr<<"Adding NSEC ordering information"<<endl;
113  else if(!narrow)
114    cerr<<"Adding NSEC3 hashed ordering information for '"<<zone<<"'"<<endl;
115  else 
116    cerr<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl;
117 
118  BOOST_FOREACH(const string& qname, qnames)
119  {
120    string shorter(qname);
121    bool auth=true;
122    do {
123      if(nsset.count(shorter)) { 
124        auth=false;
125        break;
126      }
127    }while(chopOff(shorter));
128
129    if(!haveNSEC3) // NSEC
130      sd.db->updateDNSSECOrderAndAuth(sd.domain_id, zone, qname, auth);
131    else {
132      if(!narrow) {
133        hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, qname)));
134        cerr<<"'"<<qname<<"' -> '"<< hashed <<"'"<<endl;
135      }
136      sd.db->updateDNSSECOrderAndAuthAbsolute(sd.domain_id, qname, hashed, auth);
137    }
138  }
139  cerr<<"Done listing"<<endl;
140}
141
142void checkZone(DNSSECKeeper& dk, const std::string& zone)
143{
144  UeberBackend* B = new UeberBackend("default");
145  SOAData sd;
146 
147  if(!B->getSOA(zone, sd)) {
148    cerr<<"No SOA!"<<endl;
149    return;
150  } 
151  sd.db->list(zone, sd.domain_id);
152  DNSResourceRecord rr;
153  uint64_t numrecords=0, numerrors=0;
154 
155  while(sd.db->get(rr)) {
156    if(rr.qtype.getCode() == QType::MX) 
157      rr.content = lexical_cast<string>(rr.priority)+" "+rr.content;
158    if(rr.auth == 0 && rr.qtype.getCode()!=QType::NS && rr.qtype.getCode()!=QType::A)
159    {
160      cerr<<"Following record is auth=0, run pdnssec rectify-zone?: "<<rr.qname<<" IN " <<rr.qtype.getName()<< " " << rr.content<<endl;
161    }
162    try {
163      shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
164      string tmp=drc->serialize(rr.qname);
165    }
166    catch(std::exception& e) 
167    {
168      cerr<<"Following record had a problem: "<<rr.qname<<" IN " <<rr.qtype.getName()<< " " << rr.content<<endl;
169      cerr<<"Error was: "<<e.what()<<endl;
170      numerrors++;
171    }
172    numrecords++;
173  }
174  cerr<<"Checked "<<numrecords<<" records, "<<numerrors<<" errors"<<endl;
175}
176
177void showZone(DNSSECKeeper& dk, const std::string& zone)
178{
179  if(!dk.isSecuredZone(zone)) {
180        cerr<<"Zone is not secured\n";
181        return;
182  }
183  NSEC3PARAMRecordContent ns3pr;
184  bool narrow;
185  bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
186 
187  if(!haveNSEC3) 
188    cout<<"Zone has NSEC semantics"<<endl;
189  else
190    cout<<"Zone has " << (narrow ? "NARROW " : "") <<"hashed NSEC3 semantics, configuration: "<<ns3pr.getZoneRepresentation()<<endl;
191 
192  cout <<"Zone is " << (dk.isPresigned(zone) ? "" : "not ") << "presigned\n";
193 
194  DNSSECKeeper::keyset_t keyset=dk.getKeys(zone);
195
196  if(keyset.empty())  {
197    cerr << "No keys for zone '"<<zone<<"'."<<endl;
198  }
199  else { 
200    cout << "keys: "<<endl;
201    BOOST_FOREACH(DNSSECKeeper::keyset_t::value_type value, keyset) {
202      cout<<"ID = "<<value.second.id<<" ("<<(value.second.keyOrZone ? "KSK" : "ZSK")<<"), tag = "<<value.first.getDNSKEY().getTag();
203      cout<<", algo = "<<(int)value.first.d_algorithm<<", bits = "<<value.first.d_key.getConstContext().len*8<<"\tActive: "<<value.second.active<< endl; // humanTime(value.second.beginValidity)<<" - "<<humanTime(value.second.endValidity)<<endl;
204      if(value.second.keyOrZone) {
205        cout<<"KSK DNSKEY = "<<zone<<" IN DNSKEY "<< value.first.getDNSKEY().getZoneRepresentation() << endl;
206        cout<<"DS = "<<zone<<" IN DS "<<makeDSFromDNSKey(zone, value.first.getDNSKEY(), 1).getZoneRepresentation() << endl;
207        cout<<"DS = "<<zone<<" IN DS "<<makeDSFromDNSKey(zone, value.first.getDNSKEY(), 2).getZoneRepresentation() << endl << endl;
208      }
209    }
210  }
211}
212
213int main(int argc, char** argv)
214try
215{
216  po::options_description desc("Allowed options");
217  desc.add_options()
218    ("help,h", "produce help message")
219    ("verbose,v", po::value<bool>(), "be verbose")
220    ("force", "force an action")
221    ("config-name", po::value<string>()->default_value(""), "virtual configuration name")
222    ("config-dir", po::value<string>()->default_value(SYSCONFDIR), "location of pdns.conf")
223    ("commands", po::value<vector<string> >());
224
225  po::positional_options_description p;
226  p.add("commands", -1);
227  po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), g_vm);
228  po::notify(g_vm);
229
230  vector<string> cmds;
231
232  if(g_vm.count("commands")) 
233    cmds = g_vm["commands"].as<vector<string> >();
234
235  if(cmds.empty() || g_vm.count("help")) {
236    cerr<<"Usage: \npdnssec [options] [show-zone] [secure-zone] [rectify-zone] [add-zone-key] [deactivate-zone-key] [remove-zone-key] [activate-zone-key]\n";
237    cerr<<"         [import-zone-key] [export-zone-key] [set-nsec3] [unset-nsec3] [export-zone-dnskey]\n\n";
238    cerr<<"activate-zone-key ZONE KEY-ID   Activate the key with key id KEY-ID in ZONE\n";
239    cerr<<"add-zone-key ZONE [zsk|ksk]     Add a ZSK or KSK to a zone\n";
240    cerr<<"  [bits] [rsasha1|rsasha256]    and specify algorithm & bits\n";
241    cerr<<"check-zone ZONE                 Check a zone for correctness\n";
242    cerr<<"deactivate-zone-key             Dectivate the key with key id KEY-ID in ZONE\n";
243    cerr<<"export-zone-dnskey ZONE KEY-ID  Export to stdout the public DNSKEY described\n";
244    cerr<<"export-zone-key ZONE KEY-ID     Export to stdout the private key described\n";
245    cerr<<"import-zone-key ZONE FILE       Import from a file a private key, ZSK or KSK\n";           
246    cerr<<"                [ksk|zsk]       Defaults to KSK\n";
247    cerr<<"rectify-zone ZONE               Fix up DNSSEC fields (order, auth)\n";
248    cerr<<"remove-zone-key ZONE KEY-ID     Remove key with KEY-ID from ZONE\n";
249    cerr<<"secure-zone                     Add KSK and two ZSKs\n";
250    cerr<<"set-nsec3 'params' [narrow]     Enable NSEC3 with PARAMs. Optionally narrow\n";
251    cerr<<"show-zone ZONE                  Show DNSSEC (public) key details about a zone\n";
252    cerr<<"unset-nsec3 ZONE                Switch back to NSEC\n\n";
253
254    cerr<<"Options:"<<endl;
255    cerr<<desc<<endl;
256    return 0;
257  }
258 
259  loadMainConfig(g_vm["config-dir"].as<string>());
260  reportAllTypes();
261  DNSSECKeeper dk;
262
263  if(cmds[0] == "rectify-zone" || cmds[0] == "order-zone") {
264    if(cmds.size() != 2) {
265      cerr << "Error: "<<cmds[0]<<" takes exactly 1 parameter"<<endl;
266      return 0;
267    }
268    rectifyZone(dk, cmds[1]);
269  }
270  else if(cmds[0] == "check-zone") {
271    if(cmds.size() != 2) {
272      cerr << "Error: "<<cmds[0]<<" takes exactly 1 parameter"<<endl;
273      return 0;
274    }
275    checkZone(dk, cmds[1]);
276  }
277
278  else if(cmds[0] == "show-zone") {
279    if(cmds.size() != 2) {
280      cerr << "Error: "<<cmds[0]<<" takes exactly 1 parameter"<<endl;
281      return 0;
282    }
283    const string& zone=cmds[1];
284    showZone(dk, zone);
285  }
286  else if(cmds[0] == "activate-zone-key") {
287    const string& zone=cmds[1];
288    unsigned int id=atoi(cmds[2].c_str());
289    dk.activateKey(zone, id);
290  }
291  else if(cmds[0] == "deactivate-zone-key") {
292    const string& zone=cmds[1];
293    unsigned int id=atoi(cmds[2].c_str());
294    dk.deactivateKey(zone, id);
295  }
296  else if(cmds[0] == "add-zone-key") {
297    const string& zone=cmds[1];
298    // need to get algorithm, bits & ksk or zsk from commandline
299    bool keyOrZone=false;
300    int bits=0;
301    int algorithm=5;
302    for(unsigned int n=2; n < cmds.size(); ++n) {
303      if(pdns_iequals(cmds[n], "zsk"))
304        keyOrZone = false;
305      else if(pdns_iequals(cmds[n], "ksk"))
306        keyOrZone = true;
307      else if(pdns_iequals(cmds[n], "rsasha1"))
308        algorithm=5;
309      else if(pdns_iequals(cmds[n], "rsasha256"))
310        algorithm=8;
311      else if(atoi(cmds[n].c_str()))
312        bits = atoi(cmds[n].c_str());
313      else { 
314        cerr<<"Unknown algorithm, key flag or size '"<<cmds[n]<<"'"<<endl;
315        return 0;
316      }
317    }
318    cerr<<"Adding a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<endl;
319    if(bits)
320      cerr<<"Requesting specific key size of "<<bits<<" bits"<<endl;
321    dk.addKey(zone, keyOrZone, algorithm, bits); 
322  }
323  else if(cmds[0] == "remove-zone-key") {
324    if(cmds.size() < 3) {
325      cerr<<"Syntax: pdnssec remove-zone-key ZONE KEY-ID\n";
326      return 0;
327    }
328    const string& zone=cmds[1];
329    unsigned int id=atoi(cmds[2].c_str());
330    dk.removeKey(zone, id);
331  }
332 
333  else if(cmds[0] == "secure-zone") {
334    if(cmds.size() != 2) {
335      cerr << "Error: "<<cmds[0]<<" takes exactly 1 parameter"<<endl;
336      return 0;
337    }
338    const string& zone=cmds[1];
339    DNSSECPrivateKey dpk;
340   
341    if(dk.isSecuredZone(zone)) {
342      cerr << "Zone '"<<zone<<"' already secure, remove with pdnssec remove-zone-key if needed"<<endl;
343      return 0;
344    }
345     
346    dk.secureZone(zone, 8);
347
348    if(!dk.isSecuredZone(zone)) {
349      cerr << "This should not happen, still no key!" << endl;
350      return 0;
351    }
352 
353    DNSSECKeeper::keyset_t zskset=dk.getKeys(zone, false);
354
355    if(!zskset.empty())  {
356      cerr<<"There were ZSKs already for zone '"<<zone<<"', no need to add more"<<endl;
357      return 0;
358    }
359     
360    dk.addKey(zone, false, 8);
361    dk.addKey(zone, false, 8, 0, false); // not active
362    rectifyZone(dk, zone);
363    showZone(dk, zone);
364  }
365  else if(cmds[0]=="set-nsec3") {
366    string nsec3params =  cmds.size() > 2 ? cmds[2] : "1 0 1 ab";
367    bool narrow = cmds.size() > 3 && cmds[3]=="narrow";
368    NSEC3PARAMRecordContent ns3pr(nsec3params);
369    dk.setNSEC3PARAM(cmds[1], ns3pr, narrow);
370  }
371  else if(cmds[0]=="set-presigned") {
372        if(cmds.size() < 2) {
373                cerr<<"Wrong number of arguments, syntax: set-presigned DOMAIN"<<endl;
374        }
375    dk.setPresigned(cmds[1]);
376  }
377  else if(cmds[0]=="unset-presigned") {
378        if(cmds.size() < 2) {
379                cerr<<"Wrong number of arguments, syntax: unset-presigned DOMAIN"<<endl;
380        }
381    dk.unsetPresigned(cmds[1]);
382  }
383  else if(cmds[0]=="hash-zone-record") {
384    if(cmds.size() < 3) {
385      cerr<<"Wrong number of arguments, syntax: hash-zone-record ZONE RECORD"<<endl;
386      return 0;
387    }
388    string& zone=cmds[1];
389    string& record=cmds[2];
390    NSEC3PARAMRecordContent ns3pr;
391    bool narrow;
392    if(!dk.getNSEC3PARAM(zone, &ns3pr, &narrow)) {
393      cerr<<"The '"<<zone<<"' zone does not use NSEC3"<<endl;
394      return 0;
395    }
396    if(!narrow) {
397      cerr<<"The '"<<zone<<"' zone uses narrow NSEC3, but calculating hash anyhow"<<endl;
398    }
399     
400    cout<<toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, record)))<<endl;
401  }
402  else if(cmds[0]=="unset-nsec3") {
403    dk.unsetNSEC3PARAM(cmds[1]);
404  }
405  else if(cmds[0]=="export-zone-key") {
406    if(cmds.size() < 3) {
407      cerr<<"Syntax: pdnssec export-zone-key zone-name id"<<endl;
408      cerr<<cmds.size()<<endl;
409      exit(1);
410    }
411
412    string zone=cmds[1];
413    unsigned int id=atoi(cmds[2].c_str());
414    DNSSECPrivateKey dpk=dk.getKeyById(zone, id);
415    cout << dpk.d_key.convertToISC(dpk.d_algorithm) <<endl;
416  } 
417  else if(cmds[0]=="import-zone-key-pem") {
418    if(cmds.size() < 4) {
419      cerr<<"Syntax: pdnssec import-zone-key zone-name filename.pem algorithm [zsk|ksk]"<<endl;
420      exit(1);
421    }
422    string zone=cmds[1];
423    string fname=cmds[2];
424    string line;
425    ifstream ifs(fname.c_str());
426    string tmp, interim, raw;
427    while(getline(ifs, line)) {
428      if(line[0]=='-')
429        continue;
430      trim(line);
431      interim += line;
432    }
433    B64Decode(interim, raw);
434    DNSSECPrivateKey dpk;
435    getRSAKeyFromPEMString(&dpk.d_key.getContext(), raw);
436   
437    dpk.d_algorithm = atoi(cmds[3].c_str());
438   
439    if(dpk.d_algorithm == 7)
440      dpk.d_algorithm = 5;
441     
442    cerr<<(int)dpk.d_algorithm<<endl;
443   
444    if(cmds.size() > 4) {
445      if(pdns_iequals(cmds[4], "ZSK"))
446        dpk.d_flags = 256;
447      else if(pdns_iequals(cmds[4], "KSK"))
448        dpk.d_flags = 257;
449      else {
450        cerr<<"Unknown key flag '"<<cmds[4]<<"'\n";
451        exit(1);
452      }
453    }
454    else
455      dpk.d_flags = 257; // ksk
456     
457    dk.addKey(zone, dpk); 
458   
459  }
460  else if(cmds[0]=="import-zone-key") {
461    if(cmds.size() < 3) {
462      cerr<<"Syntax: pdnssec import-zone-key zone-name filename [zsk|ksk]"<<endl;
463      exit(1);
464    }
465    string zone=cmds[1];
466    string fname=cmds[2];
467    DNSSECPrivateKey dpk;
468    DNSKEYRecordContent drc = getRSAKeyFromISC(&dpk.d_key.getContext(), fname.c_str());
469    dpk.d_algorithm = drc.d_algorithm;
470   
471    if(dpk.d_algorithm == 7)
472      dpk.d_algorithm = 5;
473     
474    cerr<<(int)dpk.d_algorithm<<endl;
475   
476    if(cmds.size() > 3) {
477      if(pdns_iequals(cmds[3], "ZSK"))
478        dpk.d_flags = 256;
479      else if(pdns_iequals(cmds[3], "KSK"))
480        dpk.d_flags = 257;
481      else {
482        cerr<<"Unknown key flag '"<<cmds[3]<<"'\n";
483        exit(1);
484      }
485    }
486    else
487      dpk.d_flags = 257; 
488     
489    dk.addKey(zone, dpk); 
490  }
491  else if(cmds[0]=="export-zone-dnskey") {
492    if(cmds.size() < 3) {
493      cerr<<"Syntax: pdnssec export-zone-dnskey zone-name id"<<endl;
494      exit(1);
495    }
496
497    string zone=cmds[1];
498    unsigned int id=atoi(cmds[2].c_str());
499    DNSSECPrivateKey dpk=dk.getKeyById(zone, id);
500    cout << zone<<" IN DNSKEY "<<dpk.getDNSKEY().getZoneRepresentation() <<endl;
501    if(dpk.d_flags == 257) {
502      cout << zone << " IN DS "<<makeDSFromDNSKey(zone, dpk.getDNSKEY(), 1).getZoneRepresentation() << endl;
503      cout << zone << " IN DS "<<makeDSFromDNSKey(zone, dpk.getDNSKEY(), 2).getZoneRepresentation() << endl;
504    }
505  }
506  else {
507    cerr<<"Unknown command '"<<cmds[0]<<"'\n";
508    return 1;
509  }
510  return 0;
511}
512catch(AhuException& ae) {
513  cerr<<"Error: "<<ae.reason<<endl;
514}
515catch(std::exception& e) {
516  cerr<<"Error: "<<e.what()<<endl;
517}
Note: See TracBrowser for help on using the browser.