]> granicus.if.org Git - pdns/commitdiff
and the actual toysdig
authorbert hubert <bert.hubert@netherlabs.nl>
Thu, 5 Nov 2015 08:58:33 +0000 (09:58 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Thu, 5 Nov 2015 08:58:33 +0000 (09:58 +0100)
pdns/Makefile.am
pdns/toysdig.cc

index 41fe9a0ca2ee8f42cc646180d7f66ec09a00cab0..078e23245d2e9a34fbd52da045884e88435e62df 100644 (file)
@@ -721,10 +721,13 @@ toysdig_SOURCES = \
        dnslabeltext.cc \
        dnsparser.cc dnsparser.hh \
        dnsrecords.cc \
+       dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
        ednssubnet.cc ednssubnet.hh \
+       gss_context.cc gss_context.hh \
        logger.cc \
        mbedtlscompat.hh \
+       mbedtlssigners.cc \
        misc.cc misc.hh \
        nsecrecords.cc \
        qtype.cc \
@@ -736,7 +739,12 @@ toysdig_SOURCES = \
        toysdig.cc \
        unix_utility.cc
 
+
 toysdig_LDADD = $(MBEDTLS_LIBS)
+if GSS_TSIG
+toysdig_LDADD += $(GSS_LIBS)
+endif
+
 
 tsig_tests_SOURCES = \
        arguments.cc \
index 5db9b703673cf04eab75d44387c501fde14b6ebd..0b7d395068b2f154d27a832549610a059ca097e6 100644 (file)
 #include "dnsrecords.hh"
 #include "statbag.hh"
 #include "ednssubnet.hh"
+#include "dnssecinfra.hh"
+#include "recursor_cache.hh"
+#include "base32.hh"
 StatBag S;
 
+class TCPResolver : public boost::noncopyable
+{
+public:
+  TCPResolver(ComboAddress addr) : d_rsock(AF_INET, SOCK_STREAM)
+  {
+    d_rsock.connect(addr);
+  }
+
+  string query(const DNSName& qname, uint16_t qtype)
+  {
+    cerr<<"Q "<<qname<<"/"<<DNSRecordContent::NumberToType(qtype)<<endl;
+    vector<uint8_t> packet;
+    DNSPacketWriter pw(packet, qname, qtype);
+
+    // recurse
+    pw.getHeader()->rd=true;
+
+    // we'll do the validation
+    pw.getHeader()->cd=true;
+    pw.getHeader()->ad=true;
+
+    // we do require DNSSEC records to do that!
+    pw.addOpt(2800, 0, EDNSOpts::DNSSECOK);
+    pw.commit();
+
+    uint16_t len;
+    len = htons(packet.size());
+    if(d_rsock.write((char *) &len, 2) != 2)
+      throw PDNSException("tcp write failed");
+
+    d_rsock.writen(string((char*)&*packet.begin(), (char*)&*packet.end()));
+    
+    if(d_rsock.read((char *) &len, 2) != 2)
+      throw PDNSException("tcp read failed");
+
+    len=ntohs(len);
+    char *creply = new char[len];
+    int n=0;
+    int numread;
+    while(n<len) {
+      numread=d_rsock.read(creply+n, len-n);
+      if(numread<0)
+        throw PDNSException("tcp read failed");
+      n+=numread;
+    }
+
+    string reply(creply, len);
+    delete[] creply;
+
+    return reply;
+  }
+
+  Socket d_rsock;
+};
+
+
+set<DSRecordContent> g_anchors;
+
+vector<DNSKEYRecordContent> getKeys(Socket& sock, const DNSName& name, uint16_t tag, bool* trusted)
+{
+  *trusted=false;
+  vector<uint8_t> packet;
+  DNSPacketWriter pw(packet, name, QType::DNSKEY);
+  pw.getHeader()->rd=1;
+  pw.getHeader()->cd=1;
+  pw.getHeader()->id=getpid();
+  pw.addOpt(1800, 0, EDNSOpts::DNSSECOK);
+  pw.commit();
+  
+  sock.send(string((char*)&*packet.begin(), (char*)&*packet.end()));
+  string resp;
+  sock.read(resp);
+  MOADNSParser mdp(resp);
+  vector<DNSKEYRecordContent> ret;
+  shared_ptr<DNSKEYRecordContent> root;
+  for(const auto& r : mdp.d_answers) {
+    cout<<r.first.d_place-1<<"\t"<<r.first.d_name.toString()<<"\tIN\t"<<DNSRecordContent::NumberToType(r.first.d_type);
+    cout<<"\t"<<r.first.d_content->getZoneRepresentation()<<endl;
+
+    if(auto dk = getRR<DNSKEYRecordContent>(r.first)) {
+      if(dk->getTag() == tag) {
+       cout<<"Found key with tag "<<dk->getTag()<<endl;
+       ret.push_back(*dk);
+      }
+      else {
+       cout<<"Also got other key with tag "<<dk->getTag()<<" and DS ";
+       auto ds=makeDSFromDNSKey(name, *dk, 2);
+       cout<<ds.getZoneRepresentation()<<endl;
+       if(g_anchors.count(ds)) {
+         cout<<"THIS IS THE ROOT"<<endl;
+         root = dk;
+       }
+      }
+    }
+    else if(auto rrsig = getRR<RRSIGRecordContent>(r.first)) {
+      if(root) {
+       cout<<"Had a signature on "<<r.first.d_name<<" "<<DNSRecordContent::NumberToType(r.first.d_type)<<" with tag "<<rrsig->d_tag<<endl;
+       cout<<"Have a root dnskey with tag "<<rrsig->d_tag<<endl;
+       auto engine = DNSCryptoKeyEngine::makeFromPublicKeyString(rrsig->d_algorithm, root->d_key);
+       vector<shared_ptr<DNSRecordContent>> toSign;
+       toSign.push_back(std::make_shared<DNSKEYRecordContent>(ret[0]));
+       toSign.push_back(root);
+       string msg = getMessageForRRSET(r.first.d_name, *rrsig, toSign);        
+       *trusted=engine->verify(msg, rrsig->d_signature);
+       cout<<"Result for signature in DNSKEY: "<<*trusted<<endl;
+      }
+    }
+  }
+  return ret;
+}
+
+
+unique_ptr<MOADNSParser> getMDP(const ComboAddress& dest, const DNSName& qname, uint16_t qtype)
+{
+  TCPResolver tr(dest);
+
+  string resp=tr.query(qname, qtype);
+  return unique_ptr<MOADNSParser>(new MOADNSParser(resp));
+
+}
+
+
+void lookup(const ComboAddress& dest, const DNSName& qname, uint16_t qtype)
+{
+  if(qname==DNSName(".") && qtype == QType::DS) {
+    cerr<<"Hit root, should stop somehow ;-)"<<endl;
+    exit(0);
+  }
+  Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
+  sock.connect(dest);
+  vector<uint8_t> packet;
+
+  DNSPacketWriter pw(packet, qname, qtype);
+  pw.getHeader()->rd=1;
+  pw.getHeader()->cd=1;
+  pw.getHeader()->id=getpid();
+  pw.addOpt(1800, 0, EDNSOpts::DNSSECOK);
+  pw.commit();
+
+  sock.send(string((char*)&*packet.begin(), (char*)&*packet.end()));
+  string resp;
+  sock.read(resp);
+  MOADNSParser mdp(resp);
+
+  struct ContentPair {
+    vector<DNSRecord> content;
+    vector<shared_ptr<RRSIGRecordContent>> signatures;
+  };
+
+  map<pair<DNSName,uint16_t>, ContentPair > records;
+
+  for(const auto& r : mdp.d_answers) {
+    cout<<r.first.d_place-1<<"\t"<<r.first.d_name.toString()<<"\tIN\t"<<DNSRecordContent::NumberToType(r.first.d_type);
+    cout<<"\t"<<r.first.d_content->getZoneRepresentation()<<endl;
+
+    if(auto rrsig = getRR<RRSIGRecordContent>(r.first)) {
+      records[make_pair(r.first.d_name, rrsig->d_type)].signatures.push_back(rrsig);
+    }
+    else if(auto opt = getRR<OPTRecordContent>(r.first)) {
+      continue;
+    }
+
+    else
+      records[make_pair(r.first.d_name, r.first.d_type)].content.push_back(r.first);
+
+  }
+  cout<<"Had "<<records.size()<<" RRSETs"<<endl;
+  for(auto& rrset : records) {
+    vector<shared_ptr<DNSRecordContent> > toSign;
+    for(const auto& c : rrset.second.content) 
+      toSign.push_back(c.d_content);
+
+    for(auto& sign : rrset.second.signatures) {
+      cout<<"Seeing if we can retrieve DNSKEY for "<<sign->d_signer<<" with tag "<<sign->d_tag<<endl;
+      bool trusted=false;
+      auto keys=getKeys(sock, sign->d_signer, sign->d_tag, &trusted);
+      cout<<"Got "<<keys.size()<<" keys"<<endl;
+      for(const auto& key : keys) {
+       auto engine = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key);
+       string msg = getMessageForRRSET(rrset.first.first, *sign, toSign);        
+       cout<<"Result for signature on "<<rrset.first.first<<" "<<DNSRecordContent::NumberToType(rrset.first.second)<<": "<<engine->verify(msg, sign->d_signature)<<endl;
+
+       if(trusted) {
+         cerr<<"This key is trusted ultimately"<<endl;
+         return;
+       }
+       else {
+
+         cerr<<"Should go looking for DS on DNSKEY "<<sign->d_signer<<endl;
+         lookup(dest, sign->d_signer, QType::DS);
+       }
+      }
+    }
+  }
+}
+
+// 4033 5
+// FIXME: namespace this
+enum vState { Indeterminate, Bogus, Insecure, Secure };
+const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure"};
+
+// NSEC(3) results
+// FIXME: namespace this
+enum dState { NODATA, NXDOMAIN, ENT, INSECURE };
+const char *dStates[]={"nodata", "nxdomain", "empty non-terminal", "insecure (no-DS proof)"};
+
+
+typedef std::multimap<uint16_t, DNSKEYRecordContent> keymap_t;
+
+
+string nsec3Hash(const DNSName &qname, const NSEC3RecordContent& nrc)
+{
+  NSEC3PARAMRecordContent ns3pr;
+  ns3pr.d_iterations = nrc.d_iterations;
+  ns3pr.d_salt = nrc.d_salt;
+  return toBase32Hex(hashQNameWithSalt(ns3pr, qname));
+}
+
+
+
+
+typedef map<pair<DNSName, uint16_t>, set<shared_ptr<DNSRecordContent> > > rrsetmap_t;
+
+typedef pair<DNSName, uint16_t> NT; // Name/Type pair
+typedef std::multimap<NT, shared_ptr<DNSRecordContent> > recmap_t;
+typedef std::multimap<NT, RRSIGRecordContent> sigmap_t;
+typedef std::multimap<NT, shared_ptr<DNSRecordContent> > nsecmap_t;
+
+typedef pair<DNSName, uint16_t> ZT; //Zonename/keyTag pair
+// recmap_t g_recs; // fetched recs for full chain validation
+// keymap_t g_keys; // fetched keys
+// keymap_t g_vkeys; // validated keys
+
+// FIXME: needs a zone argument, to avoid things like 6840 4.1
+dState getDenial(rrsetmap_t &validrrsets, DNSName qname, uint16_t qtype)
+{
+  std::multimap<DNSName, NSEC3RecordContent> nsec3s;
+
+  for(rrsetmap_t::const_iterator i=validrrsets.begin(); i!=validrrsets.end(); ++i)
+  {
+    // FIXME also support NSEC
+    if(i->first.second != QType::NSEC3) continue;
+    
+    for(set<shared_ptr<DNSRecordContent> >::const_iterator j=i->second.begin(); j!=i->second.end(); ++j) {
+      NSEC3RecordContent ns3r = dynamic_cast<NSEC3RecordContent&> (**j);
+      // nsec3.insert(new nsec3()
+      // cerr<<toBase32Hex(r.d_nexthash)<<endl;
+      nsec3s.insert(make_pair(i->first.first, ns3r));
+    }
+  }
+  cerr<<"got "<<nsec3s.size()<<" NSEC3s"<<endl;
+  for(auto i=nsec3s.begin(); i != nsec3s.end(); ++i) {
+    vector<string> parts = i->first.getRawLabels();
+
+      string base=toLower(parts[0]);
+      string next=toLower(toBase32Hex(i->second.d_nexthash));
+      string hashed = nsec3Hash(qname, i->second);
+      cerr<<base<<" .. ? "<<hashed<<" ("<<qname<<") ? .. "<<next<<endl;
+      if(base==hashed) {
+        // positive name proof, need to check type
+        cerr<<"positive name proof, checking type bitmap"<<endl;
+        cerr<<"d_set.count("<<qtype<<"): "<<i->second.d_set.count(qtype)<<endl;
+        if(qtype == QType::DS && i->second.d_set.count(qtype) == 0) return INSECURE; // FIXME need to require 'NS in bitmap' here, otherwise no delegation! (but first, make sure this is reliable - does not work that way for direct auth queries)
+      } else if ((hashed > base && hashed < next) ||
+                (next < base && (hashed < next || hashed > base))) {
+        bool optout=(1 & i->second.d_flags);
+        cerr<<"negative name proof, optout = "<<optout<<endl;
+        if(qtype == QType::DS && optout) return INSECURE;
+      }
+  }
+  dState ret;
+  return ret;
+}
+/*
+
+string dotEscape(string name)
+{
+  return "\"" + boost::replace_all_copy(name, "\"", "\\\"") + "\"";
+}
+
+string dotName(string type, string name, string tag)
+{
+  if(tag == "")
+    return type+" "+name;
+  else
+    return type+" "+name+"/"+tag;
+}
+void dotNode(string type, string name, string tag, string content)
+{
+  cout<<"    "
+      <<dotEscape(dotName(type, name, tag))
+      <<" [ label="<<dotEscape(dotName(type, name, tag)+"\\n"+content)<<" ];"<<endl;
+}
+
+void dotEdge(DNSName zone, string type1, DNSName name1, DNSName tag1, string type2, DNSName name2, DNSName tag2, string color="")
+{
+  cout<<"    ";
+  if(zone != DNSName(".")) cout<<"subgraph "<<dotEscape("cluster "+zone.toString())<<" { ";
+  cout<<dotEscape(dotName(type1, name1, tag1))
+      <<" -> "
+      <<dotEscape(dotName(type2, name2, tag2));
+  if(color != "") cout<<" [ color=\""<<color<<"\" ]; ";
+  else cout<<"; ";
+  if(zone != DNSName(".")) cout<<"label = "<<dotEscape("zone: "+zone.toString())<<";"<<"}";
+  cout<<endl;
+}
+*/
+
+void validateWithKeySet(rrsetmap_t& rrsets, rrsetmap_t& rrsigs, rrsetmap_t& validated, keymap_t& keys)
+{
+  for(rrsetmap_t::const_iterator i=rrsets.begin(); i!=rrsets.end(); i++)
+  {
+    cerr<<"validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<endl;
+    pair<rrsetmap_t::const_iterator, rrsetmap_t::const_iterator> r = rrsigs.equal_range(make_pair(i->first.first, i->first.second));
+    for(rrsetmap_t::const_iterator j=r.first; j!=r.second; j++) {
+      for(set<shared_ptr<DNSRecordContent> >::const_iterator k=j->second.begin(); k!=j->second.end(); k++) {
+        vector<shared_ptr<DNSRecordContent> > toSign;
+        set<shared_ptr<DNSRecordContent> > rrs = rrsets[make_pair(j->first.first, j->first.second)];
+        toSign.assign(rrs.begin(), rrs.end());
+
+        const RRSIGRecordContent rrc=dynamic_cast<const RRSIGRecordContent&> (*(*k));
+
+        if(!keys.count(rrc.d_tag)) continue;
+
+        string msg=getMessageForRRSET(j->first.first, rrc, toSign);
+        pair<keymap_t::const_iterator, keymap_t::const_iterator> r = keys.equal_range(rrc.d_tag); // FIXME: also take algorithm into account? right now we wrongly validate unknownalgorithm.bad-dnssec.wb.sidnlabs.nl
+        for(keymap_t::const_iterator l=r.first; l!=r.second; l++) {
+          bool isValid = DNSCryptoKeyEngine::makeFromPublicKeyString(l->second.d_algorithm, l->second.d_key)->verify(msg, rrc.d_signature);
+          if(isValid) {
+            validated[make_pair(j->first.first, j->first.second)] = rrs;
+            cerr<<"valid"<<endl;
+            cerr<<"! validated "<<j->first.first<<"/"<<DNSRecordContent::NumberToType(rrc.d_type)<<endl;
+          }
+          if(rrc.d_type != QType::DNSKEY) {
+           /*              dotEdge(rrc.d_signer,
+                    "DNSKEY", rrc.d_signer, lexical_cast<string>(rrc.d_tag),
+                    DNSRecordContent::NumberToType(rrc.d_type), j->first.first, "", isValid ? "green" : "red");
+           */
+          }
+          // FIXME: break out enough levels
+        }
+      }
+    }
+  }
+}
+
+// returns vState
+// should return vState, zone cut and validated keyset
+// i.e. www.7bits.nl -> insecure/7bits.nl/[]
+//      www.powerdnssec.org -> secure/powerdnssec.org/[keys]
+//      www.dnssec-failed.org -> bogus/dnssec-failed.org/[]
+
+char *rootDS;
+
+vState getKeysFor(TCPResolver& tr, const DNSName& zone, keymap_t &keyset)
+{
+  vector<string> labels = zone.getRawLabels();
+  vState state;
+
+  state = Indeterminate;
+
+  DNSName qname;
+  typedef std::multimap<uint16_t, DSRecordContent> dsmap_t;
+  dsmap_t dsmap;
+  keymap_t keymap;
+  recmap_t recs;
+
+  state = Secure;
+  while(zone.isPartOf(qname))
+  {
+    if(qname.isRoot())
+    {
+      DSRecordContent rootanchor=dynamic_cast<DSRecordContent&> (*(DNSRecordContent::mastermake(QType::DS, 1, rootDS)));
+      dsmap.clear();
+      dsmap.insert(make_pair(rootanchor.d_tag, rootanchor));
+    }
+  
+    recs.clear();
+    vector<RRSIGRecordContent> sigs;
+    vector<shared_ptr<DNSRecordContent> > toSign;
+    vector<uint16_t> toSignTags;
+
+    keymap_t tkeymap; // tentative keys
+    keymap.clear();
+    
+    // start of this iteration
+    // we can trust that dsmap has valid DS records for qname
+
+    cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl;
+    MOADNSParser mdp(tr.query(qname, (uint16_t)QType::DNSKEY));
+
+    for(auto i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
+      if(i->first.d_name != qname)
+        continue;
+
+      if(i->first.d_type == QType::RRSIG)
+      {
+        RRSIGRecordContent rrc=dynamic_cast<RRSIGRecordContent&> (*(i->first.d_content));
+        if(rrc.d_type != QType::DNSKEY)
+          continue;
+        sigs.push_back(rrc);
+      }
+      else if(i->first.d_type == QType::DNSKEY)
+      {
+        DNSKEYRecordContent drc=dynamic_cast<DNSKEYRecordContent&> (*(i->first.d_content));
+        tkeymap.insert(make_pair(drc.getTag(), drc));
+       //        dotNode("DNSKEY", qname, lexical_cast<string>(drc.getTag()), (boost::format("tag=%d, algo=%d") % drc.getTag() % static_cast<int>(drc.d_algorithm)).str());
+
+        toSign.push_back(i->first.d_content);
+        toSignTags.push_back(drc.getTag());
+      }
+    }
+    cerr<<"got "<<tkeymap.size()<<" keys and "<<sigs.size()<<" sigs"<<endl;
+
+    for(dsmap_t::const_iterator i=dsmap.begin(); i!=dsmap.end(); i++)
+    {
+      DSRecordContent dsrc=i->second;
+      cerr<<"got DS with tag "<<dsrc.d_tag<<"/"<<i->first<<", got "<<tkeymap.count(i->first)<<" DNSKEYs for tag"<<endl;
+      pair<keymap_t::const_iterator, keymap_t::const_iterator> r = tkeymap.equal_range(i->first);
+      for(keymap_t::const_iterator j=r.first; j!=r.second; j++)
+      {
+        DNSKEYRecordContent drc=j->second;
+        DSRecordContent dsrc2=makeDSFromDNSKey(qname, drc, dsrc.d_digesttype);
+        bool isValid = dsrc == dsrc2;
+        if(isValid) {
+          cerr<<"got valid DNSKEY"<<endl;
+          keymap.insert(make_pair(drc.getTag(), drc));
+         //          dotNode("DS", qname, "" /*lexical_cast<string>(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast<int>(dsrc.d_digesttype) % static_cast<int>(dsrc.d_algorithm)).str());
+        }
+        // cout<<"    subgraph "<<dotEscape("cluster "+qname)<<" { "<<dotEscape("DS "+qname)<<" -> "<<dotEscape("DNSKEY "+qname)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<qname<<"\"; }"<<endl;
+       //        dotEdge("", "DS", qname, "" /*lexical_cast<string>(dsrc.d_tag)*/, "DNSKEY", qname, lexical_cast<string>(drc.getTag()), isValid ? "green" : "red");
+        // dotNode("DNSKEY", qname, (boost::format("tag=%d, algo=%d") % drc.getTag() % static_cast<int>(drc.d_algorithm)).str());
+      }
+    }
+
+    cerr<<"got "<<keymap.size()<<"/"<<tkeymap.size()<<" valid/tentative keys"<<endl;
+    // these counts could be off if we somehow ended up with 
+    // duplicate keys. Should switch to a type that prevents that.
+    if(keymap.size() < tkeymap.size())
+    {
+      // this should mean that we have one or more DS-validated DNSKEYs
+      // but not a fully validated DNSKEY set, yet
+      // one of these valid DNSKEYs should be able to validate the
+      // whole set
+      for(auto i=sigs.begin(); i!=sigs.end(); i++)
+      {
+        cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<tkeymap.count(i->d_tag)<<" keys of which "<<keymap.count(i->d_tag)<<" valid"<<endl;
+        string msg=getMessageForRRSET(qname, *i, toSign);
+        pair<keymap_t::const_iterator, keymap_t::const_iterator> r = keymap.equal_range(i->d_tag);
+        for(keymap_t::const_iterator j=r.first; j!=r.second; j++) {
+          cerr<<"validating"<<endl;
+          bool isValid = DNSCryptoKeyEngine::makeFromPublicKeyString(j->second.d_algorithm, j->second.d_key)->verify(msg, i->d_signature);
+         //          for(uint16_t tag : toSignTags) {
+           //            dotEdge(qname,
+           //      "DNSKEY", qname, lexical_cast<string>(i->d_tag),
+           //      "DNSKEY", qname, lexical_cast<string>(tag), isValid ? "green" : "red");
+         //          }
+
+          if(isValid)
+          {
+            cerr<<"validation succeeded - whole DNSKEY set is valid"<<endl;
+            // cout<<"    "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+qname)<<";"<<endl;
+            keymap=tkeymap;
+            break;
+          }
+        }
+        if(!keymap.size()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
+      }
+    }
+
+    if(!keymap.size())
+    {
+      cerr<<"ended up with zero valid DNSKEYs, going Bogus"<<endl;
+      state=Bogus;
+      break;
+    }
+    cerr<<"situation: we have one or more valid DNSKEYs for ["<<qname<<"] (want ["<<zone<<"])"<<endl;
+    if(qname == zone) {
+      cerr<<"requested keyset found! returning Secure"<<endl;
+      keyset.insert(keymap.begin(), keymap.end());
+      return Secure;
+    }
+    cerr<<"walking downwards to find DS"<<endl;
+    DNSName keyqname=qname;
+    do {
+      qname=DNSName(labels.back())+qname;
+      labels.pop_back();
+      cerr<<"next name ["<<qname<<"], trying to get DS"<<endl;
+
+      recs.clear();
+      sigs.clear();
+      dsmap_t tdsmap; // tentative DSes
+      dsmap.clear();
+      toSign.clear();
+      toSignTags.clear();
+
+      MOADNSParser mdp(tr.query(qname, QType::DS));
+
+      rrsetmap_t rrsets;
+      rrsetmap_t rrsigs;
+
+      for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
+        cerr<<"res "<<i->first.d_name<<"/"<<i->first.d_type<<endl;
+        if(i->first.d_type == QType::OPT) continue;
+
+        if(i->first.d_type == QType::RRSIG) {
+          RRSIGRecordContent rrc = dynamic_cast<RRSIGRecordContent&> (*(i->first.d_content));
+          rrsigs[make_pair(i->first.d_name,rrc.d_type)].insert(i->first.d_content);
+        }
+        else {
+          rrsets[make_pair(i->first.d_name,i->first.d_type)].insert(i->first.d_content);
+        }
+      }
+
+      rrsetmap_t validrrsets;
+      validateWithKeySet(rrsets, rrsigs, validrrsets, keymap);
+
+      cerr<<"got "<<rrsets.count(make_pair(qname,QType::DS))<<" DS of which "<<validrrsets.count(make_pair(qname,QType::DS))<<" valid "<<endl;
+
+      auto r = validrrsets.equal_range(make_pair(qname, QType::DS));
+      for(rrsetmap_t::const_iterator i=r.first; i!=r.second; i++) {
+        for(set<shared_ptr<DNSRecordContent> >::const_iterator j=i->second.begin(); j!=i->second.end(); j++)
+        {
+          const DSRecordContent dsrc=dynamic_cast<const DSRecordContent&> (**j);
+          dsmap.insert(make_pair(dsrc.d_tag, dsrc));
+          // dotEdge(keyqname,
+          //         "DNSKEY", keyqname, ,
+          //         "DS", qname, lexical_cast<string>(dsrc.d_tag));
+          // cout<<"    "<<dotEscape("DNSKEY "+keyqname)<<" -> "<<dotEscape("DS "+qname)<<";"<<endl;
+        }
+      }
+      if(!dsmap.size()) {
+        cerr<<"no DS at this level, checking for denials"<<endl;
+        dState dres = getDenial(validrrsets, qname, QType::DS);
+        if(dres == INSECURE) return Insecure;
+      }
+    } while(!dsmap.size() && labels.size());
+
+    // break;
+  }
+
+  return state;
+}
+
+
+
+
 int main(int argc, char** argv)
 try
 {
   reportAllTypes();
-
+  g_anchors.insert(DSRecordContent("19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5"));
   if(argc < 4) {
     cerr<<"Syntax: sdig IP-address port question question-type\n";
     exit(EXIT_FAILURE);
   }
+  ComboAddress dest(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
+  DNSName qname(argv[3]);
+  uint16_t qtype=DNSRecordContent::TypeToNumber(argv[4]);
+  TCPResolver tr(dest);
 
+  cout<<"digraph oneshot {"<<endl;
 
-  Socket sock(AF_INET, SOCK_DGRAM);
-  ComboAddress dest(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
-  for(unsigned int n=0; n < 20000; ++n) {
-    vector<uint8_t> packet;
-    string qname;
+  auto mdp=getMDP(dest, qname, qtype);
 
-    if(!(n%20))
-      qname=boost::lexical_cast<string>(n)+".ds9a.nl";
-    else
-      qname=boost::lexical_cast<string>(n)+"."+argv[3];
-    
-    DNSPacketWriter pw(packet, DNSName(qname), DNSRecordContent::TypeToNumber(argv[4]));
+  rrsetmap_t rrsets;
+  rrsetmap_t rrsigs;
 
-    pw.getHeader()->rd=1;
+  for(auto i = mdp->d_answers.begin(); i != mdp->d_answers.end(); ++i) {
+    cerr<<"res "<<i->first.d_name<<"/"<<i->first.d_type<<endl;
+    if(i->first.d_type == QType::OPT) continue;
 
-    pw.getHeader()->id=n;
-    pw.addOpt(1800, 0, EDNSOpts::DNSSECOK);
-    pw.commit();
+    if(i->first.d_type == QType::RRSIG) {
+      RRSIGRecordContent rrc = dynamic_cast<RRSIGRecordContent&> (*(i->first.d_content));
+      rrsigs[make_pair(i->first.d_name,rrc.d_type)].insert(i->first.d_content);
+    }
+    else {
+      rrsets[make_pair(i->first.d_name,i->first.d_type)].insert(i->first.d_content);
+    }
+  }
+
+  cerr<<"got "<<rrsigs.size()<<" sigs for "<<rrsets.size()<<" sets"<<endl;
 
-    sock.sendTo(string((char*)&*packet.begin(), (char*)&*packet.end()), dest);
-    usleep(100);
+  pair<rrsetmap_t::const_iterator, rrsetmap_t::const_iterator> r = rrsigs.equal_range(make_pair(qname, qtype));
+  cerr<<"got "<<rrsigs.count(make_pair(qname, qtype))<<" applicable sigs"<<endl;
+
+  keymap_t keys;
+  rrsetmap_t validrrsets;
+
+  if(rrsigs.size()) {
+    for(rrsetmap_t::const_iterator i=rrsigs.begin(); i!=rrsigs.end(); i++) {
+      cerr<<"A"<<endl;
+      for(set<shared_ptr<DNSRecordContent> >::const_iterator j=i->second.begin(); j!=i->second.end(); j++)
+      {
+        cerr<<"B"<<endl;
+        const RRSIGRecordContent rrc=dynamic_cast<const RRSIGRecordContent&> (*(*j));
+        cerr<<"got rrsig "<<rrc.d_signer<<"/"<<rrc.d_tag<<endl;
+        vState state = getKeysFor(tr, rrc.d_signer, keys);
+        cerr<<"! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
+        // dsmap.insert(make_pair(dsrc.d_tag, dsrc));
+      }
+    }
+    validateWithKeySet(rrsets, rrsigs, validrrsets, keys);
   }
-  
+  else {
+    cerr<<"no sigs, hoping for Insecure"<<endl;
+    vState state = getKeysFor(tr, qname, keys);
+    cerr<<"! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
+  }
+  cerr<<"! validated "<<validrrsets.size()<<" RRsets out of "<<rrsets.size()<<endl;
+
+  cerr<<"% validated RRs:"<<endl;
+  for(rrsetmap_t::const_iterator i=validrrsets.begin(); i!=validrrsets.end(); i++) {
+    cerr<<"% "<<i->first.first<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<endl;
+    for(set<shared_ptr<DNSRecordContent> >::const_iterator j=i->second.begin(); j!=i->second.end(); j++) {
+      cerr<<"% > "<<(*j)->getZoneRepresentation()<<endl;
+    }
+  }
+
+  cout<<"}"<<endl;
+  exit(0);
+
 
 
 }
 catch(std::exception &e)