1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include "dnsparser.hh"
5 #include "dnsrecords.hh"
6 #include "dnswriter.hh"
7 #include "ednsoptions.hh"
8 #include "ednssubnet.hh"
9 #include "misc.hh"
10 #include "proxy-protocol.hh"
11 #include "sstuff.hh"
12 #include "statbag.hh"
13 #include <boost/array.hpp>
14 
15 #ifdef HAVE_LIBCURL
16 #include "minicurl.hh"
17 #endif
18 
19 #include "tcpiohandler.hh"
20 
21 StatBag S;
22 
23 // Vars below used by tcpiohandler.cc
24 bool g_verbose = true;
25 bool g_syslog = false;
26 
27 static bool hidettl = false;
28 
ttl(uint32_t ttl)29 static string ttl(uint32_t ttl)
30 {
31   if (hidettl)
32     return "[ttl]";
33   else
34     return std::to_string(ttl);
35 }
36 
usage()37 static void usage()
38 {
39   cerr << "sdig" << endl;
40   cerr << "Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QNAME QTYPE "
41           "[dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] [recurse] [showflags] "
42           "[tcp] [dot] [insecure] [fastOpen] [subjectName name] [caStore file] [tlsProvider openssl|gnutls] "
43           "[xpf XPFDATA] [class CLASSNUM] "
44           "[proxy UDP(0)/TCP(1) SOURCE-IP-ADDRESS-AND-PORT DESTINATION-IP-ADDRESS-AND-PORT]"
45           "dumpluaraw"
46        << endl;
47 }
48 
nameForClass(QClass qclass,uint16_t qtype)49 static const string nameForClass(QClass qclass, uint16_t qtype)
50 {
51   if (qtype == QType::OPT)
52     return "IN";
53 
54   return qclass.toString();
55 }
56 
57 static std::unordered_set<uint16_t> s_expectedIDs;
58 
fillPacket(vector<uint8_t> & packet,const string & q,const string & t,bool dnssec,const boost::optional<Netmask> ednsnm,bool recurse,uint16_t xpfcode,uint16_t xpfversion,uint64_t xpfproto,char * xpfsrc,char * xpfdst,QClass qclass,uint16_t qid)59 static void fillPacket(vector<uint8_t>& packet, const string& q, const string& t,
60                        bool dnssec, const boost::optional<Netmask> ednsnm,
61                        bool recurse, uint16_t xpfcode, uint16_t xpfversion,
62                        uint64_t xpfproto, char* xpfsrc, char* xpfdst,
63                        QClass qclass, uint16_t qid)
64 {
65   DNSPacketWriter pw(packet, DNSName(q), DNSRecordContent::TypeToNumber(t), qclass);
66 
67   if (dnssec || ednsnm || getenv("SDIGBUFSIZE")) {
68     char* sbuf = getenv("SDIGBUFSIZE");
69     int bufsize;
70     if (sbuf)
71       bufsize = atoi(sbuf);
72     else
73       bufsize = 2800;
74     DNSPacketWriter::optvect_t opts;
75     if (ednsnm) {
76       EDNSSubnetOpts eo;
77       eo.source = *ednsnm;
78       opts.push_back(
79         make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo)));
80     }
81 
82     pw.addOpt(bufsize, 0, dnssec ? EDNSOpts::DNSSECOK : 0, opts);
83     pw.commit();
84   }
85 
86   if (xpfcode) {
87     ComboAddress src(xpfsrc), dst(xpfdst);
88     pw.startRecord(g_rootdnsname, xpfcode, 0, QClass::IN, DNSResourceRecord::ADDITIONAL);
89     // xpf->toPacket(pw);
90     pw.xfr8BitInt(xpfversion);
91     pw.xfr8BitInt(xpfproto);
92     pw.xfrCAWithoutPort(xpfversion, src);
93     pw.xfrCAWithoutPort(xpfversion, dst);
94     pw.xfrCAPort(src);
95     pw.xfrCAPort(dst);
96     pw.commit();
97   }
98 
99   if (recurse) {
100     pw.getHeader()->rd = true;
101   }
102 
103   pw.getHeader()->id = htons(qid);
104 }
105 
printReply(const string & reply,bool showflags,bool hidesoadetails,bool dumpluaraw)106 static void printReply(const string& reply, bool showflags, bool hidesoadetails, bool dumpluaraw)
107 {
108   MOADNSParser mdp(false, reply);
109   if (!s_expectedIDs.count(ntohs(mdp.d_header.id))) {
110     cout << "ID " << ntohs(mdp.d_header.id) << " was not expected, this response was not meant for us!"<<endl;
111   }
112   s_expectedIDs.erase(ntohs(mdp.d_header.id));
113 
114   cout << "Reply to question for qname='" << mdp.d_qname.toString()
115        << "', qtype=" << DNSRecordContent::NumberToType(mdp.d_qtype) << endl;
116   cout << "Rcode: " << mdp.d_header.rcode << " ("
117        << RCode::to_s(mdp.d_header.rcode) << "), RD: " << mdp.d_header.rd
118        << ", QR: " << mdp.d_header.qr;
119   cout << ", TC: " << mdp.d_header.tc << ", AA: " << mdp.d_header.aa
120        << ", opcode: " << mdp.d_header.opcode << endl;
121 
122   for (MOADNSParser::answers_t::const_iterator i = mdp.d_answers.begin();
123        i != mdp.d_answers.end(); ++i) {
124     cout << i->first.d_place - 1 << "\t" << i->first.d_name.toString() << "\t"
125          << nameForClass(i->first.d_class, i->first.d_type) << "\t"
126          << DNSRecordContent::NumberToType(i->first.d_type);
127     if (dumpluaraw) {
128       cout<<"\t"<< makeLuaString(i->first.d_content->serialize(DNSName(), true))<<endl;
129       continue;
130     }
131     if (i->first.d_class == QClass::IN) {
132       if (i->first.d_type == QType::RRSIG) {
133         string zoneRep = i->first.d_content->getZoneRepresentation();
134         vector<string> parts;
135         stringtok(parts, zoneRep);
136         cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
137              << parts[1] << " " << parts[2] << " " << parts[3]
138              << " [expiry] [inception] [keytag] " << parts[7] << " ...\n";
139         continue;
140       }
141       if (!showflags && i->first.d_type == QType::NSEC3) {
142         string zoneRep = i->first.d_content->getZoneRepresentation();
143         vector<string> parts;
144         stringtok(parts, zoneRep);
145         cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " [flags] "
146              << parts[2] << " " << parts[3] << " " << parts[4];
147         for (vector<string>::iterator iter = parts.begin() + 5;
148              iter != parts.end(); ++iter)
149           cout << " " << *iter;
150         cout << "\n";
151         continue;
152       }
153       if (i->first.d_type == QType::DNSKEY) {
154         string zoneRep = i->first.d_content->getZoneRepresentation();
155         vector<string> parts;
156         stringtok(parts, zoneRep);
157         cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
158              << parts[1] << " " << parts[2] << " ...\n";
159         continue;
160       }
161       if (i->first.d_type == QType::SOA && hidesoadetails) {
162         string zoneRep = i->first.d_content->getZoneRepresentation();
163         vector<string> parts;
164         stringtok(parts, zoneRep);
165         cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
166              << parts[1] << " [serial] " << parts[3] << " " << parts[4] << " "
167              << parts[5] << " " << parts[6] << "\n";
168         continue;
169       }
170     }
171     cout << "\t" << ttl(i->first.d_ttl) << "\t"
172          << i->first.d_content->getZoneRepresentation() << "\n";
173   }
174 
175   EDNSOpts edo;
176   if (getEDNSOpts(mdp, &edo)) {
177     //    cerr<<"Have "<<edo.d_options.size()<<" options!"<<endl;
178     for (vector<pair<uint16_t, string>>::const_iterator iter = edo.d_options.begin();
179          iter != edo.d_options.end(); ++iter) {
180       if (iter->first == EDNSOptionCode::ECS) { // 'EDNS subnet'
181         EDNSSubnetOpts reso;
182         if (getEDNSSubnetOptsFromString(iter->second, &reso)) {
183           cerr << "EDNS Subnet response: " << reso.source.toString()
184                << ", scope: " << reso.scope.toString()
185                << ", family = " << reso.scope.getNetwork().sin4.sin_family
186                << endl;
187         }
188       } else if (iter->first == EDNSOptionCode::PADDING) {
189         cerr << "EDNS Padding size: " << (iter->second.size()) << endl;
190       } else {
191         cerr << "Have unknown option " << (int)iter->first << endl;
192       }
193     }
194   }
195 }
196 
main(int argc,char ** argv)197 int main(int argc, char** argv)
198 try {
199   /* default timeout of 10s */
200   int timeout = 10;
201   bool dnssec = false;
202   bool recurse = false;
203   bool tcp = false;
204   bool showflags = false;
205   bool hidesoadetails = false;
206   bool doh = false;
207   bool dot = false;
208   bool fastOpen = false;
209   bool insecureDoT = false;
210   bool fromstdin = false;
211   boost::optional<Netmask> ednsnm;
212   uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
213   char *xpfsrc = NULL, *xpfdst = NULL;
214   QClass qclass = QClass::IN;
215   string proxyheader;
216   string subjectName;
217   string caStore;
218   string tlsProvider = "openssl";
219   bool dumpluaraw = false;
220 
221   for (int i = 1; i < argc; i++) {
222     if ((string)argv[i] == "--help") {
223       usage();
224       exit(EXIT_SUCCESS);
225     }
226 
227     if ((string)argv[i] == "--version") {
228       cerr << "sdig " << VERSION << endl;
229       exit(EXIT_SUCCESS);
230     }
231   }
232 
233   if (argc < 5) {
234     usage();
235     exit(EXIT_FAILURE);
236   }
237 
238   reportAllTypes();
239 
240   if (argc > 5) {
241     for (int i = 5; i < argc; i++) {
242       if (strcmp(argv[i], "dnssec") == 0)
243         dnssec = true;
244       else if (strcmp(argv[i], "recurse") == 0)
245         recurse = true;
246       else if (strcmp(argv[i], "showflags") == 0)
247         showflags = true;
248       else if (strcmp(argv[i], "hidesoadetails") == 0)
249         hidesoadetails = true;
250       else if (strcmp(argv[i], "hidettl") == 0)
251         hidettl = true;
252       else if (strcmp(argv[i], "tcp") == 0)
253         tcp = true;
254       else if (strcmp(argv[i], "dot") == 0)
255         dot = true;
256       else if (strcmp(argv[i], "insecure") == 0)
257         insecureDoT = true;
258       else if (strcmp(argv[i], "fastOpen") == 0)
259         fastOpen = true;
260       else if (strcmp(argv[i], "ednssubnet") == 0) {
261         if (argc < i + 2) {
262           cerr << "ednssubnet needs an argument" << endl;
263           exit(EXIT_FAILURE);
264         }
265         ednsnm = Netmask(argv[++i]);
266       }
267       else if (strcmp(argv[i], "xpf") == 0) {
268         if (argc < i + 6) {
269           cerr << "xpf needs five arguments" << endl;
270           exit(EXIT_FAILURE);
271         }
272         xpfcode = atoi(argv[++i]);
273         xpfversion = atoi(argv[++i]);
274         xpfproto = atoi(argv[++i]);
275         xpfsrc = argv[++i];
276         xpfdst = argv[++i];
277       }
278       else if (strcmp(argv[i], "class") == 0) {
279         if (argc < i+2) {
280           cerr << "class needs an argument"<<endl;
281           exit(EXIT_FAILURE);
282         }
283         qclass = atoi(argv[++i]);
284       }
285       else if (strcmp(argv[i], "subjectName") == 0) {
286         if (argc < i + 2) {
287           cerr << "subjectName needs an argument"<<endl;
288           exit(EXIT_FAILURE);
289         }
290         subjectName = argv[++i];
291       }
292       else if (strcmp(argv[i], "caStore") == 0) {
293         if (argc < i + 2) {
294           cerr << "caStore needs an argument"<<endl;
295           exit(EXIT_FAILURE);
296         }
297         caStore = argv[++i];
298       }
299       else if (strcmp(argv[i], "tlsProvider") == 0) {
300         if (argc < i + 2) {
301           cerr << "tlsProvider needs an argument"<<endl;
302           exit(EXIT_FAILURE);
303         }
304         tlsProvider = argv[++i];
305       }
306       else if (strcmp(argv[i], "proxy") == 0) {
307         if(argc < i+4) {
308           cerr<<"proxy needs three arguments"<<endl;
309           exit(EXIT_FAILURE);
310         }
311         bool ptcp = atoi(argv[++i]);
312         ComboAddress src(argv[++i]);
313         ComboAddress dest(argv[++i]);
314         proxyheader = makeProxyHeader(ptcp, src, dest, {});
315       }
316       else if (strcmp(argv[i], "dumpluaraw") == 0) {
317         dumpluaraw = true;
318       }
319       else {
320         cerr << argv[i] << ": unknown argument" << endl;
321         exit(EXIT_FAILURE);
322       }
323     }
324   }
325 
326   if (dot) {
327     tcp = true;
328   }
329 
330 #ifndef HAVE_DNS_OVER_TLS
331   if (dot) {
332     cerr << "DoT requested but not compiled in" << endl;
333     exit(EXIT_FAILURE);
334   }
335 #endif
336 
337   string reply;
338   ComboAddress dest;
339   if (*argv[1] == 'h') {
340     doh = true;
341   } else if(strcmp(argv[1], "stdin") == 0) {
342     fromstdin = true;
343   } else {
344     dest = ComboAddress(argv[1] + (*argv[1] == '@'), atoi(argv[2]));
345   }
346 
347   string name = string(argv[3]);
348   string type = string(argv[4]);
349 
350   vector<pair<string, string>> questions;
351   if (name == "-" && type == "-") {
352     if (!tcp) {
353       throw PDNSException("multi-query from stdin only supported for tcp");
354     }
355     string line;
356     while (getline(std::cin, line)) {
357       auto fields = splitField(line, ' ');
358 
359       questions.push_back(make_pair(fields.first, fields.second));
360     }
361   } else {
362     questions.push_back(make_pair(name, type));
363   }
364 
365   if (doh) {
366 #ifdef HAVE_LIBCURL
367     vector<uint8_t> packet;
368     s_expectedIDs.insert(0);
369     fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
370                xpfproto, xpfsrc, xpfdst, qclass, 0);
371     MiniCurl mc;
372     MiniCurl::MiniCurlHeaders mch;
373     mch.insert(std::make_pair("Content-Type", "application/dns-message"));
374     mch.insert(std::make_pair("Accept", "application/dns-message"));
375     string question(packet.begin(), packet.end());
376     // FIXME: how do we use proxyheader here?
377     reply = mc.postURL(argv[1], question, mch, timeout, fastOpen);
378     printReply(reply, showflags, hidesoadetails, dumpluaraw);
379 #else
380     throw PDNSException("please link sdig against libcurl for DoH support");
381 #endif
382   } else if (fromstdin) {
383     std::istreambuf_iterator<char> begin(std::cin), end;
384     reply = string(begin, end);
385 
386     ComboAddress source, destination;
387     bool wastcp;
388     bool proxy = false;
389     std::vector<ProxyProtocolValue> ignoredValues;
390     ssize_t offset = parseProxyHeader(reply, proxy, source, destination, wastcp, ignoredValues);
391     if (offset && proxy) {
392       cout<<"proxy "<<(wastcp ? "tcp" : "udp")<<" headersize="<<offset<<" source="<<source.toStringWithPort()<<" destination="<<destination.toStringWithPort()<<endl;
393       reply = reply.substr(offset);
394     }
395 
396     if (tcp) {
397       reply = reply.substr(2);
398     }
399 
400     printReply(reply, showflags, hidesoadetails, dumpluaraw);
401   } else if (tcp) {
402     std::shared_ptr<TLSCtx> tlsCtx{nullptr};
403     if (dot) {
404       TLSContextParameters tlsParams;
405       tlsParams.d_provider = tlsProvider;
406       tlsParams.d_validateCertificates = !insecureDoT;
407       tlsParams.d_caStore = caStore;
408       tlsCtx = getTLSContext(tlsParams);
409     }
410     uint16_t counter = 0;
411     Socket sock(dest.sin4.sin_family, SOCK_STREAM);
412     setTCPNoDelay(sock.getHandle()); // disable NAGLE, which does not play nicely with delayed ACKs
413     TCPIOHandler handler(subjectName, sock.releaseHandle(), timeout, tlsCtx, time(nullptr));
414     handler.connect(fastOpen, dest, timeout);
415     // we are writing the proxyheader inside the TLS connection. Is that right?
416     if (proxyheader.size() > 0 && handler.write(proxyheader.data(), proxyheader.size(), timeout) != proxyheader.size()) {
417       throw PDNSException("tcp write failed");
418     }
419 
420     for (const auto& it : questions) {
421       vector<uint8_t> packet;
422       s_expectedIDs.insert(counter);
423       fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, xpfcode,
424                  xpfversion, xpfproto, xpfsrc, xpfdst, qclass, counter);
425       counter++;
426 
427       // Prefer to do a single write, so that fastopen can send all the data on SYN
428       uint16_t len = packet.size();
429       string question;
430       question.reserve(sizeof(len) + packet.size());
431       question.push_back(static_cast<char>(len >> 8));
432       question.push_back(static_cast<char>(len & 0xff));
433       question.append(packet.begin(), packet.end());
434       if (handler.write(question.data(), question.size(), timeout) != question.size()) {
435         throw PDNSException("tcp write failed");
436       }
437     }
438     for (size_t i = 0; i < questions.size(); i++) {
439       uint16_t len;
440       if (handler.read((char *)&len, sizeof(len), timeout) != sizeof(len)) {
441         throw PDNSException("tcp read failed");
442       }
443       len = ntohs(len);
444       reply.resize(len);
445       if (handler.read(&reply[0], len, timeout) != len) {
446         throw PDNSException("tcp read failed");
447       }
448       printReply(reply, showflags, hidesoadetails, dumpluaraw);
449     }
450   } else // udp
451   {
452     vector<uint8_t> packet;
453     s_expectedIDs.insert(0);
454     fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
455                xpfproto, xpfsrc, xpfdst, qclass, 0);
456     string question(packet.begin(), packet.end());
457     Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
458     question = proxyheader + question;
459     sock.sendTo(question, dest);
460     int result = waitForData(sock.getHandle(), timeout);
461     if (result < 0)
462       throw std::runtime_error("Error waiting for data: " + stringerror());
463     if (!result)
464       throw std::runtime_error("Timeout waiting for data");
465     sock.recvFrom(reply, dest);
466     printReply(reply, showflags, hidesoadetails, dumpluaraw);
467   }
468 
469 } catch (std::exception& e) {
470   cerr << "Fatal: " << e.what() << endl;
471 } catch (PDNSException& e) {
472   cerr << "Fatal: " << e.reason << endl;
473 }
474