]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/filterpo.cc
Merge pull request #14021 from Habbie/auth-lua-join-whitespace
[thirdparty/pdns.git] / pdns / filterpo.cc
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
23 #include <cinttypes>
24 #include <iostream>
25
26 #include "filterpo.hh"
27 #include "namespaces.hh"
28 #include "dnsrecords.hh"
29
30 DNSFilterEngine::DNSFilterEngine()
31 {
32 }
33
34 bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
35 {
36 return findExactNamedPolicy(d_qpolName, qname, pol);
37 }
38
39 bool DNSFilterEngine::Zone::findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
40 {
41 return findExactNamedPolicy(d_propolName, qname, pol);
42 }
43
44 bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
45 {
46 if (const auto fnd = d_propolNSAddr.lookup(addr)) {
47 pol = fnd->second;
48 return true;
49 }
50 return false;
51 }
52
53 bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
54 {
55 if (const auto fnd = d_postpolAddr.lookup(addr)) {
56 pol = fnd->second;
57 return true;
58 }
59 return false;
60 }
61
62 bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
63 {
64 if (const auto fnd = d_qpolAddr.lookup(addr)) {
65 pol = fnd->second;
66 return true;
67 }
68 return false;
69 }
70
71 bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
72 {
73 if (polmap.empty()) {
74 return false;
75 }
76
77 /* for www.powerdns.com, we need to check:
78 www.powerdns.com.
79 *.powerdns.com.
80 *.com.
81 *.
82 */
83
84 std::unordered_map<DNSName, DNSFilterEngine::Policy>::const_iterator iter;
85 iter = polmap.find(qname);
86
87 if(iter != polmap.end()) {
88 pol=iter->second;
89 return true;
90 }
91
92 DNSName s(qname);
93 while(s.chopOff()){
94 iter = polmap.find(g_wildcarddnsname+s);
95 if(iter != polmap.end()) {
96 pol=iter->second;
97 return true;
98 }
99 }
100 return false;
101 }
102
103 bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
104 {
105 if (polmap.empty()) {
106 return false;
107 }
108
109 const auto& it = polmap.find(qname);
110 if (it != polmap.end()) {
111 pol = it->second;
112 return true;
113 }
114
115 return false;
116 }
117
118 DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies) const
119 {
120 // cout<<"Got question for nameserver name "<<qname<<endl;
121 std::vector<bool> zoneEnabled(d_zones.size());
122 size_t count = 0;
123 bool allEmpty = true;
124 for (const auto& z : d_zones) {
125 bool enabled = true;
126 const auto zoneName = z->getName();
127 if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
128 enabled = false;
129 }
130 else {
131 if (z->hasNSPolicies()) {
132 allEmpty = false;
133 }
134 else {
135 enabled = false;
136 }
137 }
138
139 zoneEnabled[count] = enabled;
140 ++count;
141 }
142
143 Policy pol;
144 if (!allEmpty) {
145 count = 0;
146 for(const auto& z : d_zones) {
147 if (zoneEnabled[count] && z->findExactNSPolicy(qname, pol)) {
148 // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
149 return pol;
150 }
151 ++count;
152 }
153
154 DNSName s(qname);
155 while(s.chopOff()){
156 count = 0;
157 for(const auto& z : d_zones) {
158 if (zoneEnabled[count] && z->findExactNSPolicy(g_wildcarddnsname+s, pol)) {
159 // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
160 return pol;
161 }
162 ++count;
163 }
164 }
165 }
166
167 return pol;
168 }
169
170 DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies) const
171 {
172 Policy pol;
173 // cout<<"Got question for nameserver IP "<<address.toString()<<endl;
174 for(const auto& z : d_zones) {
175 const auto zoneName = z->getName();
176 if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
177 continue;
178 }
179
180 if(z->findNSIPPolicy(address, pol)) {
181 // cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
182 return pol;
183 }
184 }
185 return pol;
186 }
187
188 DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies) const
189 {
190 // cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
191 std::vector<bool> zoneEnabled(d_zones.size());
192 size_t count = 0;
193 bool allEmpty = true;
194 for (const auto& z : d_zones) {
195 bool enabled = true;
196 const auto zoneName = z->getName();
197 if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
198 enabled = false;
199 }
200 else {
201 if (z->hasQNamePolicies()) {
202 allEmpty = false;
203 }
204 else {
205 enabled = false;
206 }
207 }
208
209 zoneEnabled[count] = enabled;
210 ++count;
211 }
212
213 Policy pol;
214 if (!allEmpty) {
215 count = 0;
216 for(const auto& z : d_zones) {
217 if (zoneEnabled[count] && z->findExactQNamePolicy(qname, pol)) {
218 // cerr<<"Had a hit on the name of the query"<<endl;
219 return pol;
220 }
221 ++count;
222 }
223
224 DNSName s(qname);
225 while(s.chopOff()){
226 count = 0;
227 for(const auto& z : d_zones) {
228 if (zoneEnabled[count] && z->findExactQNamePolicy(g_wildcarddnsname+s, pol)) {
229 // cerr<<"Had a hit on the name of the query"<<endl;
230 return pol;
231 }
232 ++count;
233 }
234 }
235 }
236
237 count = 0;
238 for(const auto& z : d_zones) {
239 if (zoneEnabled[count] && z->findClientPolicy(ca, pol)) {
240 // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
241 return pol;
242 }
243 }
244
245 return pol;
246 }
247
248 DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies) const
249 {
250 Policy pol;
251 ComboAddress ca;
252 for(const auto& r : records) {
253 if(r.d_place != DNSResourceRecord::ANSWER)
254 continue;
255 if(r.d_type == QType::A) {
256 if (auto rec = getRR<ARecordContent>(r)) {
257 ca = rec->getCA();
258 }
259 }
260 else if(r.d_type == QType::AAAA) {
261 if (auto rec = getRR<AAAARecordContent>(r)) {
262 ca = rec->getCA();
263 }
264 }
265 else
266 continue;
267
268 for(const auto& z : d_zones) {
269 const auto zoneName = z->getName();
270 if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
271 continue;
272 }
273
274 if(z->findResponsePolicy(ca, pol)) {
275 return pol;
276 }
277 }
278 }
279 return pol;
280 }
281
282 void DNSFilterEngine::assureZones(size_t zone)
283 {
284 if(d_zones.size() <= zone)
285 d_zones.resize(zone+1);
286 }
287
288 void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy&& pol)
289 {
290 pol.d_name = d_name;
291 pol.d_type = PolicyType::ClientIP;
292 d_qpolAddr.insert(nm).second=std::move(pol);
293 }
294
295 void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol)
296 {
297 pol.d_name = d_name;
298 pol.d_type = PolicyType::ResponseIP;
299 d_postpolAddr.insert(nm).second=std::move(pol);
300 }
301
302 void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
303 {
304 auto it = d_qpolName.find(n);
305
306 if (it != d_qpolName.end()) {
307 auto& existingPol = it->second;
308
309 if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
310 throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following QName: " + n.toLogString());
311 }
312
313 if (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
314 throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following QName: " + n.toLogString());
315 }
316
317 existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
318
319 std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
320 }
321 else {
322 auto& qpol = d_qpolName.insert({n, std::move(pol)}).first->second;
323 qpol.d_name = d_name;
324 qpol.d_type = PolicyType::QName;
325 }
326 }
327
328 void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy&& pol)
329 {
330 pol.d_name = d_name;
331 pol.d_type = PolicyType::NSDName;
332 d_propolName.insert({n, std::move(pol)});
333 }
334
335 void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy&& pol)
336 {
337 pol.d_name = d_name;
338 pol.d_type = PolicyType::NSIP;
339 d_propolNSAddr.insert(nm).second = std::move(pol);
340 }
341
342 bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, const Policy& pol)
343 {
344 d_qpolAddr.erase(nm);
345 return true;
346 }
347
348 bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, const Policy& pol)
349 {
350 d_postpolAddr.erase(nm);
351 return true;
352 }
353
354 bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
355 {
356 auto it = d_qpolName.find(n);
357 if (it == d_qpolName.end()) {
358 return false;
359 }
360
361 auto& existing = it->second;
362 if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
363 d_qpolName.erase(it);
364 return true;
365 }
366
367 /* for custom types, we might have more than one type,
368 and then we need to remove only the right ones. */
369 if (existing.d_custom.size() <= 1) {
370 d_qpolName.erase(it);
371 return true;
372 }
373
374 bool result = false;
375 for (auto& toRemove : pol.d_custom) {
376 for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
377 if (**it == *toRemove) {
378 existing.d_custom.erase(it);
379 result = true;
380 break;
381 }
382 }
383 }
384
385 return result;
386 }
387
388 bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& n, const Policy& pol)
389 {
390 d_propolName.erase(n); // XXX verify policy matched? =pol;
391 return true;
392 }
393
394 bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& nm, const Policy& pol)
395 {
396 d_propolNSAddr.erase(nm);
397 return true;
398 }
399
400 DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<DNSRecordContent>& custom) const
401 {
402 DNSRecord dr;
403 dr.d_name = qname;
404 dr.d_type = custom->getType();
405 dr.d_ttl = d_ttl;
406 dr.d_class = QClass::IN;
407 dr.d_place = DNSResourceRecord::ANSWER;
408 dr.d_content = custom;
409
410 if (dr.d_type == QType::CNAME) {
411 const auto content = std::dynamic_pointer_cast<CNAMERecordContent>(custom);
412 if (content) {
413 DNSName target = content->getTarget();
414 if (target.isWildcard()) {
415 target.chopOff();
416 dr.d_content = std::make_shared<CNAMERecordContent>(qname + target);
417 }
418 }
419 }
420
421 return dr;
422 }
423
424 std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName& qname, uint16_t qtype) const
425 {
426 if (d_kind != PolicyKind::Custom) {
427 throw std::runtime_error("Asking for a custom record from a filtering policy of a non-custom type");
428 }
429
430 std::vector<DNSRecord> result;
431
432 for (const auto& custom : d_custom) {
433 if (qtype != QType::ANY && qtype != custom->getType() && custom->getType() != QType::CNAME) {
434 continue;
435 }
436
437 DNSRecord dr;
438 dr.d_name = qname;
439 dr.d_type = custom->getType();
440 dr.d_ttl = d_ttl;
441 dr.d_class = QClass::IN;
442 dr.d_place = DNSResourceRecord::ANSWER;
443 dr.d_content = custom;
444
445 if (dr.d_type == QType::CNAME) {
446 const auto content = std::dynamic_pointer_cast<CNAMERecordContent>(custom);
447 if (content) {
448 DNSName target = content->getTarget();
449 if (target.isWildcard()) {
450 target.chopOff();
451 dr.d_content = std::make_shared<CNAMERecordContent>(qname + target);
452 }
453 }
454 }
455
456 result.emplace_back(getRecordFromCustom(qname, custom));
457 }
458
459 return result;
460 }
461
462 std::string DNSFilterEngine::getKindToString(DNSFilterEngine::PolicyKind kind)
463 {
464 static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
465 static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
466 rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
467 static const std::string rpzPrefix("rpz-");
468
469 switch(kind) {
470 case DNSFilterEngine::PolicyKind::NoAction:
471 return noaction.toString();
472 case DNSFilterEngine::PolicyKind::Drop:
473 return drop.toString();
474 case DNSFilterEngine::PolicyKind::NXDOMAIN:
475 return g_rootdnsname.toString();
476 case PolicyKind::NODATA:
477 return g_wildcarddnsname.toString();
478 case DNSFilterEngine::PolicyKind::Truncate:
479 return truncate.toString();
480 default:
481 throw std::runtime_error("Unexpected DNSFilterEngine::Policy kind");
482 }
483 }
484
485 std::string DNSFilterEngine::getTypeToString(DNSFilterEngine::PolicyType type)
486 {
487 switch(type) {
488 case DNSFilterEngine::PolicyType::None:
489 return "none";
490 case DNSFilterEngine::PolicyType::QName:
491 return "QName";
492 case DNSFilterEngine::PolicyType::ClientIP:
493 return "Client IP";
494 case DNSFilterEngine::PolicyType::ResponseIP:
495 return "Response IP";
496 case DNSFilterEngine::PolicyType::NSDName:
497 return "Name Server Name";
498 case DNSFilterEngine::PolicyType::NSIP:
499 return "Name Server IP";
500 default:
501 throw std::runtime_error("Unexpected DNSFilterEngine::Policy type");
502 }
503 }
504
505 std::vector<DNSRecord> DNSFilterEngine::Policy::getRecords(const DNSName& qname) const
506 {
507 std::vector<DNSRecord> result;
508
509 if (d_kind == PolicyKind::Custom) {
510 result = getCustomRecords(qname, QType::ANY);
511 }
512 else {
513 DNSRecord dr;
514 dr.d_name = qname;
515 dr.d_ttl = static_cast<uint32_t>(d_ttl);
516 dr.d_type = QType::CNAME;
517 dr.d_class = QClass::IN;
518 dr.d_content = DNSRecordContent::mastermake(QType::CNAME, QClass::IN, getKindToString(d_kind));
519 result.push_back(std::move(dr));
520 }
521
522 return result;
523 }
524
525 void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol)
526 {
527 auto records = pol.getRecords(name);
528 for (const auto& dr : records) {
529 fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str());
530 }
531 }
532
533 DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
534 {
535 int bits = nm.getBits();
536 DNSName res(std::to_string(bits));
537 const auto& addr = nm.getNetwork();
538
539 if (addr.isIPv4()) {
540 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&addr.sin4.sin_addr.s_addr);
541 res += DNSName(std::to_string(bytes[3]) + "." + std::to_string(bytes[2]) + "." + std::to_string(bytes[1]) + "." + std::to_string(bytes[0]));
542 }
543 else {
544 DNSName temp;
545 const auto str = addr.toString();
546 const auto len = str.size();
547 std::string::size_type begin = 0;
548
549 while (begin < len) {
550 std::string::size_type end = str.find(":", begin);
551 std::string sub;
552 if (end != string::npos) {
553 sub = str.substr(begin, end - begin);
554 }
555 else {
556 sub = str.substr(begin);
557 }
558
559 if (sub.empty()) {
560 temp = DNSName("zz") + temp;
561 }
562 else {
563 temp = DNSName(sub) + temp;
564 }
565
566 if (end == string::npos) {
567 break;
568 }
569 begin = end + 1;
570 }
571 res += temp;
572 }
573
574 return res;
575 }
576
577
578 void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol)
579 {
580 DNSName full = maskToRPZ(nm);
581 full += name;
582
583 auto records = pol.getRecords(full);
584 for (const auto& dr : records) {
585 fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str());
586 }
587 }
588
589 void DNSFilterEngine::Zone::dump(FILE* fp) const
590 {
591 /* fake the SOA record */
592 auto soa = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "fake.RPZ. hostmaster.fake.RPZ. " + std::to_string(d_serial) + " " + std::to_string(d_refresh) + " 600 3600000 604800");
593 fprintf(fp, "%s IN SOA %s\n", d_domain.toString().c_str(), soa->getZoneRepresentation().c_str());
594
595 for (const auto& pair : d_qpolName) {
596 dumpNamedPolicy(fp, pair.first + d_domain, pair.second);
597 }
598
599 for (const auto& pair : d_propolName) {
600 dumpNamedPolicy(fp, pair.first + DNSName("rpz-nsdname.") + d_domain, pair.second);
601 }
602
603 for (const auto pair : d_qpolAddr) {
604 dumpAddrPolicy(fp, pair->first, DNSName("rpz-client-ip.") + d_domain, pair->second);
605 }
606
607 for (const auto pair : d_propolNSAddr) {
608 dumpAddrPolicy(fp, pair->first, DNSName("rpz-nsip.") + d_domain, pair->second);
609 }
610
611 for (const auto pair : d_postpolAddr) {
612 dumpAddrPolicy(fp, pair->first, DNSName("rpz-ip.") + d_domain, pair->second);
613 }
614 }