]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/rpzloader.cc
Logging: have a global g_log
[thirdparty/pdns.git] / pdns / rpzloader.cc
CommitLineData
644dd1da 1#include "dnsparser.hh"
2#include "dnsrecords.hh"
4ba9d5dc 3#include "ixfr.hh"
644dd1da 4#include "syncres.hh"
39ec5d29 5#include "resolver.hh"
6#include "logger.hh"
ad42489c 7#include "rec-lua-conf.hh"
4ba9d5dc
RG
8#include "rpzloader.hh"
9#include "zoneparser-tng.hh"
644dd1da 10
11static Netmask makeNetmaskFromRPZ(const DNSName& name)
12{
13 auto parts = name.getRawLabels();
b8470add
PL
14 /*
15 * why 2?, the minimally valid IPv6 address that can be encoded in an RPZ is
16 * $NETMASK.zz (::/$NETMASK)
17 * Terrible right?
18 */
19 if(parts.size() < 2 || parts.size() > 9)
86f1af1c 20 throw PDNSException("Invalid IP address in RPZ: "+name.toLogString());
b8470add
PL
21
22 bool isV6 = (stoi(parts[0]) > 32);
23 bool hadZZ = false;
24
25 for (auto &part : parts) {
26 // Check if we have an IPv4 octet
27 for (auto c : part)
28 if (!isdigit(c))
29 isV6 = true;
30
31 if (pdns_iequals(part,"zz")) {
32 if (hadZZ)
86f1af1c 33 throw PDNSException("more than one 'zz' label found in RPZ name"+name.toLogString());
b8470add
PL
34 part = "";
35 isV6 = true;
36 hadZZ = true;
37 }
38 }
39
40 if (isV6 && parts.size() < 9 && !hadZZ)
86f1af1c 41 throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: "+name.toLogString());
b8470add
PL
42
43 if (parts.size() == 5 && !isV6)
44 return Netmask(parts[4]+"."+parts[3]+"."+parts[2]+"."+parts[1]+"/"+parts[0]);
45
46 string v6;
47
48 for (uint8_t i = parts.size()-1 ; i > 0; i--) {
49 v6 += parts[i];
50 if (parts[i] == "" && i == 1 && i == parts.size()-1)
51 v6+= "::";
52 if (parts[i] == "" && i != parts.size()-1)
53 v6+= ":";
54 if (parts[i] != "" && i != 1)
55 v6 += ":";
56 }
57 v6 += "/" + parts[0];
58
59 return Netmask(v6);
644dd1da 60}
61
6b972d59 62void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL)
644dd1da 63{
644dd1da 64 static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
644dd1da 65 static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
66 rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
39c9bef5 67 static const std::string rpzPrefix("rpz-");
39ec5d29 68
1f1ca368 69 DNSFilterEngine::Policy pol;
39ec5d29 70
ba3c54cb
RG
71 if(dr.d_class != QClass::IN) {
72 return;
73 }
74
39ec5d29 75 if(dr.d_type == QType::CNAME) {
ba3c54cb
RG
76 auto crc = getRR<CNAMERecordContent>(dr);
77 if (!crc) {
78 return;
79 }
dd079764 80 auto crcTarget=crc->getTarget();
ad42489c 81 if(defpol) {
82 pol=*defpol;
83 }
dd079764 84 else if(crcTarget.isRoot()) {
39ec5d29 85 // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": ";
86 pol.d_kind = DNSFilterEngine::PolicyKind::NXDOMAIN;
dd079764 87 } else if(crcTarget==g_wildcarddnsname) {
39ec5d29 88 // cerr<<"Wants NODATA for "<<dr.d_name<<": ";
89 pol.d_kind = DNSFilterEngine::PolicyKind::NODATA;
90 }
dd079764 91 else if(crcTarget==drop) {
39ec5d29 92 // cerr<<"Wants DROP for "<<dr.d_name<<": ";
93 pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
94 }
dd079764 95 else if(crcTarget==truncate) {
39ec5d29 96 // cerr<<"Wants TRUNCATE for "<<dr.d_name<<": ";
97 pol.d_kind = DNSFilterEngine::PolicyKind::Truncate;
98 }
dd079764 99 else if(crcTarget==noaction) {
39ec5d29 100 // cerr<<"Wants NOACTION for "<<dr.d_name<<": ";
101 pol.d_kind = DNSFilterEngine::PolicyKind::NoAction;
102 }
39c9bef5
RG
103 /* "The special RPZ encodings which are not to be taken as Local Data are
104 CNAMEs with targets that are:
105 + "." (NXDOMAIN action),
106 + "*." (NODATA action),
107 + a top level domain starting with "rpz-",
108 + a child of a top level domain starting with "rpz-".
109 */
110 else if(!crcTarget.empty() && !crcTarget.isRoot() && crcTarget.getRawLabel(crcTarget.countLabels() - 1).compare(0, rpzPrefix.length(), rpzPrefix) == 0) {
111 /* this is very likely an higher format number or a configuration error,
112 let's just ignore it. */
e6a9dde5 113 g_log<<Logger::Info<<"Discarding unsupported RPZ entry "<<crcTarget<<" for "<<dr.d_name<<endl;
39c9bef5
RG
114 return;
115 }
39ec5d29 116 else {
117 pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
118 pol.d_custom = dr.d_content;
dd079764 119 // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
39ec5d29 120 }
121 }
122 else {
db7dcbb1
RG
123 if (defpol) {
124 pol=*defpol;
125 }
126 else {
127 pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
128 pol.d_custom = dr.d_content;
129 // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": ";
130 }
39ec5d29 131 }
132
8f618901
RG
133 if (!defpol || defpol->d_ttl < 0) {
134 pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl));
135 } else {
136 pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
137 }
3876ee44 138
39ec5d29 139 // now to DO something with that
39c9bef5 140
39ec5d29 141 if(dr.d_name.isPartOf(rpzNSDname)) {
142 DNSName filt=dr.d_name.makeRelative(rpzNSDname);
143 if(addOrRemove)
6b972d59 144 zone->addNSTrigger(filt, pol);
39ec5d29 145 else
6b972d59 146 zone->rmNSTrigger(filt, pol);
6791663c 147 } else if(dr.d_name.isPartOf(rpzClientIP)) {
b8470add
PL
148 DNSName filt=dr.d_name.makeRelative(rpzClientIP);
149 auto nm=makeNetmaskFromRPZ(filt);
39ec5d29 150 if(addOrRemove)
6b972d59 151 zone->addClientTrigger(nm, pol);
39ec5d29 152 else
6b972d59 153 zone->rmClientTrigger(nm, pol);
39ec5d29 154
6791663c 155 } else if(dr.d_name.isPartOf(rpzIP)) {
39ec5d29 156 // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
b8470add
PL
157 DNSName filt=dr.d_name.makeRelative(rpzIP);
158 auto nm=makeNetmaskFromRPZ(filt);
39ec5d29 159 if(addOrRemove)
6b972d59 160 zone->addResponseTrigger(nm, pol);
39ec5d29 161 else
6b972d59 162 zone->rmResponseTrigger(nm, pol);
39ec5d29 163 } else if(dr.d_name.isPartOf(rpzNSIP)) {
b8470add
PL
164 DNSName filt=dr.d_name.makeRelative(rpzNSIP);
165 auto nm=makeNetmaskFromRPZ(filt);
166 if(addOrRemove)
6b972d59 167 zone->addNSIPTrigger(nm, pol);
b8470add 168 else
6b972d59 169 zone->rmNSIPTrigger(nm, pol);
39ec5d29 170 } else {
171 if(addOrRemove)
6b972d59 172 zone->addQNameTrigger(dr.d_name, pol);
39ec5d29 173 else
6b972d59 174 zone->rmQNameTrigger(dr.d_name, pol);
39ec5d29 175 }
176}
177
ea448a77 178shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
39ec5d29 179{
e6a9dde5 180 g_log<<Logger::Warning<<"Loading RPZ zone '"<<zoneName<<"' from "<<master.toStringWithPort()<<endl;
98c9ec39 181 if(!tt.name.empty())
e6a9dde5 182 g_log<<Logger::Warning<<"With TSIG key '"<<tt.name<<"' of algorithm '"<<tt.algo<<"'"<<endl;
98c9ec39 183
f6a8f7d7
PL
184 ComboAddress local(localAddress);
185 if (local == ComboAddress())
186 local = getQueryLocalAddress(master.sin4.sin_family, 0);
187
6b972d59 188 AXFRRetriever axfr(master, zoneName, tt, &local, maxReceivedBytes);
39ec5d29 189 unsigned int nrecords=0;
190 Resolver::res_t nop;
191 vector<DNSRecord> chunk;
192 time_t last=0;
cb6218d2
PL
193 time_t axfrStart = time(nullptr);
194 time_t axfrNow = time(nullptr);
39ec5d29 195 shared_ptr<SOARecordContent> sr;
ea448a77 196 while(axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
39ec5d29 197 for(auto& dr : chunk) {
98c9ec39 198 if(dr.d_type==QType::NS || dr.d_type==QType::TSIG) {
199 continue;
200 }
201
6b972d59 202 dr.d_name.makeUsRelative(zoneName);
39ec5d29 203 if(dr.d_type==QType::SOA) {
ba3c54cb 204 sr = getRR<SOARecordContent>(dr);
39ec5d29 205 continue;
206 }
39ec5d29 207
6b972d59 208 RPZRecordToPolicy(dr, zone, true, defpol, maxTTL);
39ec5d29 209 nrecords++;
210 }
ea448a77 211 axfrNow = time(nullptr);
cb6218d2 212 if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
ea448a77
PL
213 throw PDNSException("Total AXFR time exceeded!");
214 }
39ec5d29 215 if(last != time(0)) {
e6a9dde5 216 g_log<<Logger::Info<<"Loaded & indexed "<<nrecords<<" policy records so far"<<endl;
39ec5d29 217 last=time(0);
218 }
219 }
e6a9dde5 220 g_log<<Logger::Info<<"Done: "<<nrecords<<" policy records active, SOA: "<<sr->getZoneRepresentation()<<endl;
39ec5d29 221 return sr;
222}
223
c823f860 224// this function is silent - you do the logging
6b972d59 225void loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL)
39ec5d29 226{
227 ZoneParserTNG zpt(fname);
228 DNSResourceRecord drr;
644dd1da 229 DNSName domain;
230 while(zpt.get(drr)) {
644dd1da 231 try {
232 if(drr.qtype.getCode() == QType::CNAME && drr.content.empty())
233 drr.content=".";
234 DNSRecord dr(drr);
235 if(dr.d_type == QType::SOA) {
6791663c
RG
236 domain = dr.d_name;
237 zone->setDomain(domain);
644dd1da 238 }
39ec5d29 239 else if(dr.d_type == QType::NS) {
240 continue;
241 }
242 else {
644dd1da 243 dr.d_name=dr.d_name.makeRelative(domain);
6b972d59 244 RPZRecordToPolicy(dr, zone, true, defpol, maxTTL);
644dd1da 245 }
246 }
6791663c 247 catch(const PDNSException& pe) {
86f1af1c 248 throw PDNSException("Issue parsing '"+drr.qname.toLogString()+"' '"+drr.content+"' at "+zpt.getLineOfFile()+": "+pe.reason);
644dd1da 249 }
250 }
644dd1da 251}
4ba9d5dc 252
20c37dec 253static std::unordered_map<std::string, shared_ptr<rpzStats> > s_rpzStats;
4ba9d5dc
RG
254static std::mutex s_rpzStatsMutex;
255
20c37dec 256shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone)
4ba9d5dc
RG
257{
258 std::lock_guard<std::mutex> l(s_rpzStatsMutex);
20c37dec
PL
259 if (s_rpzStats.find(zone) == s_rpzStats.end()) {
260 s_rpzStats[zone] = std::make_shared<rpzStats>();
261 }
4ba9d5dc
RG
262 return s_rpzStats[zone];
263}
264
265static void incRPZFailedTransfers(const std::string& zone)
266{
20c37dec
PL
267 auto stats = getRPZZoneStats(zone);
268 if (stats != nullptr)
269 stats->d_failedTransfers++;
4ba9d5dc
RG
270}
271
272static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool wasAXFR)
273{
20c37dec
PL
274 auto stats = getRPZZoneStats(zone);
275 if (stats == nullptr)
276 return;
277 stats->d_successfulTransfers++;
4ba9d5dc 278 if (wasAXFR) {
20c37dec 279 stats->d_fullTransfers++;
4ba9d5dc 280 }
20c37dec
PL
281 stats->d_lastUpdate = time(nullptr);
282 stats->d_serial = serial;
283 stats->d_numberOfRecords = numberOfRecords;
4ba9d5dc
RG
284}
285
286void RPZIXFRTracker(const ComboAddress& master, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, std::shared_ptr<DNSFilterEngine::Zone> zone, const uint16_t axfrTimeout)
287{
288 uint32_t refresh = zone->getRefresh();
289 DNSName zoneName = zone->getDomain();
290 std::string polName = zone->getName() ? *(zone->getName()) : zoneName.toString();
291 shared_ptr<SOARecordContent> sr;
292
293 while (!sr) {
294 try {
295 sr=loadRPZFromServer(master, zoneName, zone, defpol, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout);
296 if(refresh == 0) {
297 refresh = sr->d_st.refresh;
298 }
299 zone->setSerial(sr->d_st.serial);
300 setRPZZoneNewState(polName, sr->d_st.serial, zone->size(), true);
301 }
302 catch(const std::exception& e) {
e6a9dde5 303 g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.what()<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl;
4ba9d5dc
RG
304 incRPZFailedTransfers(polName);
305 }
306 catch(const PDNSException& e) {
e6a9dde5 307 g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.reason<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl;
4ba9d5dc
RG
308 incRPZFailedTransfers(polName);
309 }
310
311 if (!sr) {
312 if (refresh == 0) {
313 sleep(10);
314 } else {
315 sleep(refresh);
316 }
317 }
318 }
319
320 for(;;) {
321 DNSRecord dr;
322 dr.d_content=sr;
323
324 sleep(refresh);
325
e6a9dde5 326 g_log<<Logger::Info<<"Getting IXFR deltas for "<<zoneName<<" from "<<master.toStringWithPort()<<", our serial: "<<getRR<SOARecordContent>(dr)->d_st.serial<<endl;
4ba9d5dc
RG
327 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > deltas;
328
329 ComboAddress local(localAddress);
330 if (local == ComboAddress())
331 local = getQueryLocalAddress(master.sin4.sin_family, 0);
332
333 try {
334 deltas = getIXFRDeltas(master, zoneName, dr, tt, &local, maxReceivedBytes);
335 } catch(std::runtime_error& e ){
e6a9dde5 336 g_log<<Logger::Warning<<e.what()<<endl;
4ba9d5dc
RG
337 incRPZFailedTransfers(polName);
338 continue;
339 }
340 if(deltas.empty())
341 continue;
e6a9dde5 342 g_log<<Logger::Info<<"Processing "<<deltas.size()<<" delta"<<addS(deltas)<<" for RPZ "<<zoneName<<endl;
4ba9d5dc
RG
343
344 auto luaconfsLocal = g_luaconfs.getLocal();
345 const std::shared_ptr<DNSFilterEngine::Zone> oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
346 /* we need to make a _full copy_ of the zone we are going to work on */
347 std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
348
349 int totremove=0, totadd=0;
350 bool fullUpdate = false;
351 for(const auto& delta : deltas) {
352 const auto& remove = delta.first;
353 const auto& add = delta.second;
354 if(remove.empty()) {
e6a9dde5 355 g_log<<Logger::Warning<<"IXFR update is a whole new zone"<<endl;
4ba9d5dc
RG
356 newZone->clear();
357 fullUpdate = true;
358 }
359 for(const auto& rr : remove) { // should always contain the SOA
360 if(rr.d_type == QType::NS)
361 continue;
362 if(rr.d_type == QType::SOA) {
363 auto oldsr = getRR<SOARecordContent>(rr);
364 if(oldsr && oldsr->d_st.serial == sr->d_st.serial) {
365 // cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl;
366 }
367 else
e6a9dde5 368 g_log<<Logger::Error<<"GOT WRONG SOA SERIAL REMOVAL, SHOULD TRIGGER WHOLE RELOAD"<<endl;
4ba9d5dc
RG
369 }
370 else {
371 totremove++;
e6a9dde5 372 g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl;
4ba9d5dc
RG
373 RPZRecordToPolicy(rr, newZone, false, defpol, maxTTL);
374 }
375 }
376
377 for(const auto& rr : add) { // should always contain the new SOA
378 if(rr.d_type == QType::NS)
379 continue;
380 if(rr.d_type == QType::SOA) {
381 auto newsr = getRR<SOARecordContent>(rr);
e6a9dde5 382 // g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<newsr->d_st.serial<<endl;
4ba9d5dc
RG
383 if (newsr) {
384 sr = newsr;
385 }
386 }
387 else {
388 totadd++;
e6a9dde5 389 g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl;
4ba9d5dc
RG
390 RPZRecordToPolicy(rr, newZone, true, defpol, maxTTL);
391 }
392 }
393 }
e6a9dde5 394 g_log<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<sr->d_st.serial<<endl;
4ba9d5dc
RG
395 newZone->setSerial(sr->d_st.serial);
396 setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), fullUpdate);
397
398 /* we need to replace the existing zone with the new one,
399 but we don't want to touch anything else, especially other zones,
400 since they might have been updated by another RPZ IXFR tracker thread.
401 */
402 g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
403 lci.dfe.setZone(zoneIdx, newZone);
404 });
405 }
406}