still must match obs first
[ghsmtp.git] / DNS-ldns.cpp
blob39aecaae641c5685d3f0730e798df949c424c36a
1 #include "DNS-ldns.hpp"
3 #include "DNS-iostream.hpp"
5 #include <algorithm>
6 #include <iomanip>
8 #include <cstdbool> // needs to be above ldns includes
9 #include <ldns/ldns.h>
10 #include <ldns/packet.h>
11 #include <ldns/rr.h>
13 #include <arpa/inet.h>
15 #include <glog/logging.h>
17 #include <fmt/format.h>
19 namespace DNS_ldns {
21 std::string rr_name_str(ldns_rdf const* rdf)
23 auto const sz = ldns_rdf_size(rdf);
25 if (sz > LDNS_MAX_DOMAINLEN) {
26 LOG(WARNING) << "rdf size too large";
27 return "<too long>";
29 if (sz == 1) {
30 return ""; // root label
33 auto const data = ldns_rdf_data(rdf);
35 unsigned char src_pos = 0;
36 unsigned char len = data[src_pos];
38 std::string str;
39 str.reserve(64);
40 while ((len > 0) && (src_pos < sz)) {
41 src_pos++;
42 for (unsigned char i = 0; i < len; ++i) {
43 unsigned char c = data[src_pos];
44 // if (c == '.' || c == ';' || c == '(' || c == ')' || c == '\\') {
45 if (c == '.' || c == '\\') {
46 str += '\\';
47 // str += c;
49 // else if (!(isascii(c) && isgraph(c))) {
50 // str += fmt::format("0x{:02x}", c);
51 // }
52 // else {
53 str += c;
54 // }
55 src_pos++;
57 if (src_pos < sz) {
58 str += '.';
60 len = data[src_pos];
63 if (str.length() && ('.' == str.back())) {
64 str.erase(str.length() - 1);
67 return str;
70 std::string rr_str(ldns_rdf const* rdf)
72 CHECK_NOTNULL(rdf);
74 auto const data = static_cast<char const*>(rdf->_data);
75 auto const udata = static_cast<unsigned char const*>(rdf->_data);
77 return std::string(data + 1, static_cast<std::string::size_type>(*udata));
80 Resolver::Resolver()
82 auto const status = ldns_resolver_new_frm_file(&res_, nullptr);
83 CHECK_EQ(status, LDNS_STATUS_OK) << "failed to initialize DNS resolver: "
84 << ldns_get_errorstr_by_id(status);
87 Resolver::~Resolver() { ldns_resolver_deep_free(res_); }
89 Domain::Domain(char const* domain)
90 : str_(domain)
91 , rdfp_(CHECK_NOTNULL(ldns_dname_new_frm_str(domain)))
95 Domain::Domain(std::string const& domain)
96 : str_(domain)
97 , rdfp_(CHECK_NOTNULL(ldns_dname_new_frm_str(domain.c_str())))
101 Domain::~Domain() { ldns_rdf_deep_free(rdfp_); }
103 Query::Query(Resolver const& res, DNS::RR_type type, std::string const& dom)
104 : Query(res, type, dom.c_str())
108 Query::Query(Resolver const& res, DNS::RR_type type, char const* domain)
110 Domain const dom{domain};
112 ldns_status const status = ldns_resolver_query_status(
113 &p_, res.get(), dom.get(), static_cast<ldns_enum_rr_type>(type),
114 LDNS_RR_CLASS_IN, LDNS_RD | LDNS_AD);
116 if (status != LDNS_STATUS_OK) {
117 bogus_or_indeterminate_ = true;
119 // LOG(WARNING) << "Query (" << dom.str() << "/" << type << ") "
120 // << "ldns_resolver_query_status failed: "
121 // << ldns_get_errorstr_by_id(status);
123 // If we have only one nameserver, reset the RTT otherwise all
124 // future use of this resolver object will fail.
126 ldns_resolver_set_nameserver_rtt(res.get(), 0,
127 LDNS_RESOLV_RTT_MIN); // "reachable"
130 if (p_) {
131 authentic_data_ = ldns_pkt_ad(p_);
133 truncation_ = ldns_pkt_tc(p_);
134 if (truncation_) {
135 // if UDP, retry with TCP?
136 bogus_or_indeterminate_ = true;
137 LOG(WARNING) << "truncated answer (" << dom.str() << "/" << type << ")";
140 auto const rcode = ldns_pkt_get_rcode(p_);
141 switch (rcode) {
142 case LDNS_RCODE_NOERROR: break;
144 case LDNS_RCODE_NXDOMAIN:
145 nx_domain_ = true;
146 // LOG(WARNING) << "NX domain (" << dom.str() << "/" << type << ")";
147 break;
149 case LDNS_RCODE_SERVFAIL:
150 bogus_or_indeterminate_ = true;
151 // LOG(WARNING) << "DNS server fail (" << dom.str() << "/" << type << ")";
152 break;
154 default:
155 bogus_or_indeterminate_ = true;
156 LOG(WARNING) << "DNS unknown error (" << dom.str() << "/" << type
157 << "), rcode = " << DNS::rcode_c_str(rcode) << " (" << rcode
158 << ")";
159 break;
164 Query::~Query()
166 if (p_)
167 ldns_pkt_free(p_);
170 DNS::RR_collection Query::get_records() const
172 RR_list const rrlst{*this};
173 return rrlst.get_records();
176 std::vector<std::string> Query::get_strings() const
178 RR_list const rrlst{*this};
179 return rrlst.get_strings();
182 RR_list::RR_list(Query const& q)
184 if (q.get()) {
185 // no clones, so no frees required
186 rrlst_answer_ = ldns_pkt_answer(q.get());
187 rrlst_additional_ = ldns_pkt_additional(q.get());
191 RR_list::~RR_list() {}
193 DNS::RR_collection RR_list::get_records() const
195 DNS::RR_collection ret;
197 if (rrlst_answer_) {
199 ret.reserve(ldns_rr_list_rr_count(rrlst_answer_));
201 for (unsigned i = 0; i < ldns_rr_list_rr_count(rrlst_answer_); ++i) {
202 auto const rr = ldns_rr_list_rr(rrlst_answer_, i);
204 if (rr) {
205 // LOG(INFO) << "ldns_rr_rd_count(rr) == " << ldns_rr_rd_count(rr);
207 switch (ldns_rr_get_type(rr)) {
208 case LDNS_RR_TYPE_A: {
209 CHECK_EQ(ldns_rr_rd_count(rr), 1);
210 auto const rdf = ldns_rr_rdf(rr, 0);
211 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_A);
212 ret.emplace_back(DNS::RR_A{ldns_rdf_data(rdf), ldns_rdf_size(rdf)});
213 break;
215 case LDNS_RR_TYPE_CNAME: {
216 CHECK_EQ(ldns_rr_rd_count(rr), 1);
217 auto const rdf = ldns_rr_rdf(rr, 0);
218 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_DNAME);
219 ret.emplace_back(DNS::RR_CNAME{rr_name_str(rdf)});
220 break;
222 case LDNS_RR_TYPE_PTR: {
223 CHECK_EQ(ldns_rr_rd_count(rr), 1);
224 auto const rdf = ldns_rr_rdf(rr, 0);
225 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_DNAME);
226 ret.emplace_back(DNS::RR_PTR{rr_name_str(rdf)});
227 break;
229 case LDNS_RR_TYPE_MX: {
230 CHECK_EQ(ldns_rr_rd_count(rr), 2);
231 auto const rdf_0 = ldns_rr_rdf(rr, 0);
232 CHECK_EQ(ldns_rdf_get_type(rdf_0), LDNS_RDF_TYPE_INT16);
233 auto const rdf_1 = ldns_rr_rdf(rr, 1);
234 CHECK_EQ(ldns_rdf_get_type(rdf_1), LDNS_RDF_TYPE_DNAME);
235 ret.emplace_back(
236 DNS::RR_MX{rr_name_str(rdf_1), ldns_rdf2native_int16(rdf_0)});
237 break;
239 case LDNS_RR_TYPE_TXT: {
240 CHECK_EQ(ldns_rr_rd_count(rr), 1);
241 auto const rdf = ldns_rr_rdf(rr, 0);
242 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_STR);
243 ret.emplace_back(DNS::RR_TXT{rr_str(rdf)});
244 break;
246 case LDNS_RR_TYPE_AAAA: {
247 CHECK_EQ(ldns_rr_rd_count(rr), 1);
248 auto const rdf = ldns_rr_rdf(rr, 0);
249 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_AAAA);
250 ret.emplace_back(
251 DNS::RR_AAAA{ldns_rdf_data(rdf), ldns_rdf_size(rdf)});
252 break;
254 case LDNS_RR_TYPE_TLSA: {
255 CHECK_EQ(ldns_rr_rd_count(rr), 4);
257 auto const usage{[&] {
258 auto const rdf = ldns_rr_rdf(rr, 0);
259 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_CERTIFICATE_USAGE);
260 return ldns_rdf2native_int8(rdf);
261 }()};
263 auto const selector{[&] {
264 auto const rdf = ldns_rr_rdf(rr, 1);
265 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_SELECTOR);
266 return ldns_rdf2native_int8(rdf);
267 }()};
269 auto const matching_type{[&] {
270 auto const rdf = ldns_rr_rdf(rr, 2);
271 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_MATCHING_TYPE);
272 return ldns_rdf2native_int8(rdf);
273 }()};
275 auto const rdf = ldns_rr_rdf(rr, 3);
276 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_HEX);
278 ret.emplace_back(DNS::RR_TLSA{usage, selector, matching_type,
279 ldns_rdf_data(rdf),
280 ldns_rdf_size(rdf)});
281 break;
284 default:
285 LOG(WARNING) << "unknown RR type == " << ldns_rr_get_type(rr);
286 break;
292 if (rrlst_additional_) {
293 auto const rr_count = ldns_rr_list_rr_count(rrlst_additional_);
294 if (rr_count) {
295 LOG(WARNING) << rr_count << " additional RR records";
296 for (unsigned i = 0; i < rr_count; ++i) {
297 auto const rr = ldns_rr_list_rr(rrlst_additional_, i);
298 if (rr) {
299 auto type = ldns_rr_get_type(rr);
300 LOG(WARNING) << "additional record " << i << " type "
301 << DNS::RR_type_c_str(type);
307 return ret;
310 std::vector<std::string> RR_list::get_strings() const
312 std::vector<std::string> ret;
314 if (rrlst_answer_) {
316 ret.reserve(ldns_rr_list_rr_count(rrlst_answer_));
318 for (unsigned i = 0; i < ldns_rr_list_rr_count(rrlst_answer_); ++i) {
319 auto const rr = ldns_rr_list_rr(rrlst_answer_, i);
321 if (rr) {
322 // LOG(INFO) << "ldns_rr_rd_count(rr) == " << ldns_rr_rd_count(rr);
324 switch (ldns_rr_get_type(rr)) {
325 case LDNS_RR_TYPE_A: {
326 CHECK_EQ(ldns_rr_rd_count(rr), 1);
327 auto const rdf = ldns_rr_rdf(rr, 0);
328 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_A);
329 auto const a{DNS::RR_A{ldns_rdf_data(rdf), ldns_rdf_size(rdf)}};
330 ret.emplace_back(a.c_str());
331 break;
333 case LDNS_RR_TYPE_CNAME: {
334 CHECK_EQ(ldns_rr_rd_count(rr), 1);
335 auto const rdf = ldns_rr_rdf(rr, 0);
336 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_DNAME);
337 ret.emplace_back(rr_name_str(rdf));
338 break;
340 case LDNS_RR_TYPE_PTR: {
341 CHECK_EQ(ldns_rr_rd_count(rr), 1);
342 auto const rdf = ldns_rr_rdf(rr, 0);
343 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_DNAME);
344 ret.emplace_back(rr_name_str(rdf));
345 break;
347 case LDNS_RR_TYPE_MX: {
348 CHECK_EQ(ldns_rr_rd_count(rr), 2);
349 auto const rdf_0 = ldns_rr_rdf(rr, 0);
350 CHECK_EQ(ldns_rdf_get_type(rdf_0), LDNS_RDF_TYPE_INT16);
351 auto const rdf_1 = ldns_rr_rdf(rr, 1);
352 CHECK_EQ(ldns_rdf_get_type(rdf_1), LDNS_RDF_TYPE_DNAME);
353 ret.emplace_back(rr_name_str(rdf_1));
354 break;
356 case LDNS_RR_TYPE_TXT: {
357 CHECK_EQ(ldns_rr_rd_count(rr), 1);
358 auto const rdf = ldns_rr_rdf(rr, 0);
359 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_STR);
360 ret.emplace_back(rr_str(rdf));
361 break;
363 case LDNS_RR_TYPE_AAAA: {
364 CHECK_EQ(ldns_rr_rd_count(rr), 1);
365 auto const rdf = ldns_rr_rdf(rr, 0);
366 CHECK_EQ(ldns_rdf_get_type(rdf), LDNS_RDF_TYPE_AAAA);
367 auto const a{DNS::RR_AAAA{ldns_rdf_data(rdf), ldns_rdf_size(rdf)}};
368 ret.emplace_back(a.c_str());
369 break;
371 default:
372 LOG(WARNING) << "unknown RR type == " << ldns_rr_get_type(rr);
373 break;
379 return ret;
382 DNS::RR_collection Resolver::get_records(DNS::RR_type typ,
383 char const* domain) const
385 Query const q{*this, typ, domain};
386 RR_list const rrlst{q};
387 return rrlst.get_records();
390 std::vector<std::string> Resolver::get_strings(DNS::RR_type typ,
391 char const* domain) const
393 Query const q{*this, typ, domain};
394 RR_list const rrlst{q};
395 return rrlst.get_strings();
398 } // namespace DNS_ldns