1 /*
2  * This file is part of PowerDNS or dnsdist.
3  * Copyright -- PowerDNS.COM B.V. and its contributors
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of version 2 of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * In addition, for the avoidance of any doubt, permission is granted to
10  * link this program with OpenSSL and to (re)distribute the binaries
11  * produced as the result of such linking.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "dnsparser.hh"
26 #include "sstuff.hh"
27 #include "misc.hh"
28 #include "dnswriter.hh"
29 #include "dnsrecords.hh"
30 #include "statbag.hh"
31 #include "base32.hh"
32 #include "dnssecinfra.hh"
33 
34 
35 StatBag S;
36 
37 typedef std::pair<string,string> nsec3;
38 typedef set<nsec3> nsec3set;
39 
nsec3Hash(const DNSName & qname,const string & salt,unsigned int iters)40 static string nsec3Hash(const DNSName &qname, const string &salt, unsigned int iters)
41 {
42   NSEC3PARAMRecordContent ns3prc;
43   ns3prc.d_iterations = iters;
44   ns3prc.d_salt = salt;
45   return toBase32Hex(hashQNameWithSalt(ns3prc, qname));
46 }
47 
proveOrDeny(const nsec3set & nsec3s,const DNSName & qname,const string & salt,unsigned int iters,set<DNSName> & proven,set<DNSName> & denied)48 static void proveOrDeny(const nsec3set &nsec3s, const DNSName &qname, const string &salt, unsigned int iters, set<DNSName> &proven, set<DNSName> &denied)
49 {
50   string hashed = nsec3Hash(qname, salt, iters);
51 
52   // cerr<<"proveOrDeny(.., '"<<qname<<"', ..)"<<endl;
53   // cerr<<"hashed: "<<hashed<<endl;
54   for(nsec3set::const_iterator pos=nsec3s.begin(); pos != nsec3s.end(); ++pos) {
55     string base=(*pos).first;
56     string next=(*pos).second;
57 
58     if(hashed == base)
59     {
60       proven.insert(qname);
61       cout<<qname.toString()<<" ("<<hashed<<") proven by base of "<<base<<".."<<next<<endl;
62     }
63     if(hashed == next)
64     {
65       proven.insert(qname);
66       cout<<qname.toString()<<" ("<<hashed<<") proven by next of "<<base<<".."<<next<<endl;
67     }
68     if((hashed > base && hashed < next) ||
69        (next < base && (hashed < next || hashed > base)))
70     {
71       denied.insert(qname);
72       cout<<qname.toString()<<" ("<<hashed<<") denied by "<<base<<".."<<next<<endl;
73     }
74     if (base == next && base != hashed)
75     {
76       denied.insert(qname);
77       cout<<qname.toString()<<" ("<<hashed<<") denied by "<<base<<".."<<next<<endl;
78     }
79   }
80 }
81 
usage()82 static void usage() {
83   cerr<<"nsec3dig"<<endl;
84   cerr<<"Syntax: nsec3dig IP-ADDRESS PORT QUESTION QUESTION-TYPE [recurse]\n";
85 }
86 
main(int argc,char ** argv)87 int main(int argc, char** argv)
88 try
89 {
90   bool recurse=false;
91 
92   reportAllTypes();
93 
94   for (int i = 1; i < argc; i++) {
95     if ((string) argv[i] == "--help") {
96       usage();
97       return EXIT_SUCCESS;
98     }
99 
100     if ((string) argv[i] == "--version") {
101       cerr<<"nsec3dig "<<VERSION<<endl;
102       return EXIT_SUCCESS;
103     }
104   }
105 
106   if(argc < 5) {
107     usage();
108     exit(EXIT_FAILURE);
109   }
110 
111   // FIXME: turn recurse and dnssec into proper flags or something
112   if(argc > 5 && strcmp(argv[5], "recurse")==0)
113   {
114     recurse=true;
115   }
116 
117   vector<uint8_t> packet;
118   DNSName qname(argv[3]);
119   DNSPacketWriter pw(packet, qname, DNSRecordContent::TypeToNumber(argv[4]));
120 
121   if(recurse)
122   {
123     pw.getHeader()->rd=true;
124     pw.getHeader()->cd=true;
125   }
126 
127   pw.addOpt(2800, 0, EDNSOpts::DNSSECOK);
128   pw.commit();
129 
130 
131   ComboAddress dest(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
132   Socket sock(dest.sin4.sin_family, SOCK_STREAM);
133   sock.connect(dest);
134   uint16_t len;
135   len = htons(packet.size());
136   if(sock.write((char *) &len, 2) != 2)
137     throw PDNSException("tcp write failed");
138 
139   sock.writen(string(packet.begin(), packet.end()));
140 
141   if(sock.read((char *) &len, 2) != 2)
142     throw PDNSException("tcp read failed");
143 
144   len=ntohs(len);
145   std::unique_ptr<char[]> creply(new char[len]);
146   int n=0;
147   int numread;
148   while(n<len) {
149     numread=sock.read(creply.get()+n, len-n);
150     if(numread<0)
151       throw PDNSException("tcp read failed");
152     n+=numread;
153   }
154 
155   string reply(creply.get(), len);
156 
157   MOADNSParser mdp(false, reply);
158   cout<<"Reply to question for qname='"<<mdp.d_qname<<"', qtype="<<DNSRecordContent::NumberToType(mdp.d_qtype)<<endl;
159   cout<<"Rcode: "<<mdp.d_header.rcode<<", RD: "<<mdp.d_header.rd<<", QR: "<<mdp.d_header.qr;
160   cout<<", TC: "<<mdp.d_header.tc<<", AA: "<<mdp.d_header.aa<<", opcode: "<<mdp.d_header.opcode<<endl;
161 
162   set<DNSName> names;
163   set<DNSName> namesseen;
164   set<DNSName> namestocheck;
165   nsec3set nsec3s;
166   string nsec3salt;
167   int nsec3iters = 0;
168   for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
169     if(i->first.d_type == QType::NSEC3)
170     {
171       // cerr<<"got nsec3 ["<<i->first.d_name<<"]"<<endl;
172       // cerr<<i->first.d_content->getZoneRepresentation()<<endl;
173       const auto r = std::dynamic_pointer_cast<NSEC3RecordContent>(i->first.d_content);
174       if (!r) {
175         continue;
176       }
177       // nsec3.insert(new nsec3()
178       // cerr<<toBase32Hex(r.d_nexthash)<<endl;
179       nsec3s.insert(make_pair(toLower(i->first.d_name.getRawLabel(0)), toBase32Hex(r->d_nexthash)));
180       nsec3salt = r->d_salt;
181       nsec3iters = r->d_iterations;
182     }
183     else
184     {
185       // cerr<<"namesseen.insert('"<<i->first.d_name<<"')"<<endl;
186       names.insert(i->first.d_name);
187       namesseen.insert(i->first.d_name);
188     }
189 
190     if(i->first.d_type == QType::CNAME)
191     {
192       namesseen.insert(DNSName(i->first.d_content->getZoneRepresentation()));
193     }
194 
195     cout<<i->first.d_place-1<<"\t"<<i->first.d_name.toString()<<"\tIN\t"<<DNSRecordContent::NumberToType(i->first.d_type);
196     cout<<"\t"<<i->first.d_ttl<<"\t"<< i->first.d_content->getZoneRepresentation()<<"\n";
197   }
198 
199 #if 0
200   cerr<<"got "<<names.size()<<" names"<<endl;
201   for(set<string>::const_iterator pos=names.begin(); pos != names.end(); ++pos) {
202     cerr<<"name: "<<*pos<<endl;
203   }
204   cerr<<"got "<<nsec3s.size()<<" names"<<endl;
205   for(nsec3set::const_iterator pos=nsec3s.begin(); pos != nsec3s.end(); ++pos) {
206     cerr<<"nsec3: "<<(*pos).first<<".."<<(*pos).second<<endl;
207   }
208 #endif
209 
210   cout<<"== nsec3 prove/deny report follows =="<<endl;
211   set<DNSName> proven;
212   set<DNSName> denied;
213   namesseen.insert(qname);
214   for(const auto &name: namesseen)
215   {
216     DNSName shorter(name);
217     do {
218       namestocheck.insert(shorter);
219     } while(shorter.chopOff());
220   }
221   for(const auto &name: namestocheck)
222   {
223     proveOrDeny(nsec3s, name, nsec3salt, nsec3iters, proven, denied);
224     proveOrDeny(nsec3s, g_wildcarddnsname+name, nsec3salt, nsec3iters, proven, denied);
225   }
226 
227   if(names.count(qname))
228   {
229     cout<<"== qname found in names, investigating NSEC3s in case it's a wildcard"<<endl;
230     // exit(EXIT_SUCCESS);
231   }
232   // cout<<"== qname not found in names, investigating denial"<<endl;
233   if(proven.count(qname))
234   {
235     cout<<"qname found proven, NODATA response?"<<endl;
236     exit(EXIT_SUCCESS);
237   }
238   DNSName shorter=qname;
239   DNSName encloser;
240   DNSName nextcloser;
241   DNSName prev(qname);
242   while(shorter.chopOff())
243   {
244     if(proven.count(shorter))
245     {
246       encloser=shorter;
247       nextcloser=prev;
248       cout<<"found closest encloser at "<<encloser.toString()<<endl;
249       cout<<"next closer is "<<nextcloser.toString()<<endl;
250       break;
251     }
252     prev=shorter;
253   }
254   if(encloser.countLabels() && nextcloser.countLabels())
255   {
256     if(denied.count(nextcloser))
257     {
258       cout<<"next closer ("<<nextcloser.toString()<<") is denied correctly"<<endl;
259     }
260     else
261     {
262       cout<<"next closer ("<<nextcloser.toString()<<") NOT denied"<<endl;
263     }
264     DNSName wcplusencloser=g_wildcarddnsname+encloser;
265     if(denied.count(wcplusencloser))
266     {
267       cout<<"wildcard at encloser ("<<wcplusencloser.toString()<<") is denied correctly"<<endl;
268     }
269     else if(proven.count(wcplusencloser))
270     {
271       cout<<"wildcard at encloser ("<<wcplusencloser.toString()<<") is proven"<<endl;
272     }
273     else
274     {
275       cout<<"wildcard at encloser ("<<wcplusencloser.toString()<<") is NOT denied or proven"<<endl;
276     }
277   }
278   exit(EXIT_SUCCESS);
279 }
280 catch(std::exception &e)
281 {
282   cerr<<"Fatal: "<<e.what()<<endl;
283 }
284 catch(PDNSException &e)
285 {
286   cerr<<"Fatal: "<<e.reason<<endl;
287 }
288