]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/tinydnsbackend/tinydnsbackend.cc
updated KSK and ZSK Rollover procedures, small fixes in Algorithm Rollover procedure
[thirdparty/pdns.git] / modules / tinydnsbackend / tinydnsbackend.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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "tinydnsbackend.hh"
26 #include "pdns/misc.hh"
27 #include "pdns/dnsrecords.hh"
28 #include <utility>
29
30 static string backendname = "[TinyDNSBackend] ";
31 uint32_t TinyDNSBackend::s_lastId;
32 LockGuarded<TinyDNSBackend::TDI_suffix_t> TinyDNSBackend::s_domainInfo;
33
34 vector<string> TinyDNSBackend::getLocations()
35 {
36 vector<string> ret;
37
38 if (!d_dnspacket) {
39 return ret;
40 }
41
42 //TODO: We do not have IPv6 support.
43 Netmask remote = d_dnspacket->getRealRemote();
44 if (remote.getBits() != 32) {
45 return ret;
46 }
47
48 unsigned long addr = remote.getNetwork().sin4.sin_addr.s_addr;
49
50 char key[6];
51 key[0] = '\000';
52 key[1] = '\045';
53 key[2] = (addr)&0xff;
54 key[3] = (addr >> 8) & 0xff;
55 key[4] = (addr >> 16) & 0xff;
56 key[5] = (addr >> 24) & 0xff;
57
58 for (int i = 4; i >= 0; i--) {
59 string searchkey(key, i + 2);
60 try {
61 auto reader = std::make_unique<CDB>(getArg("dbfile"));
62 ret = reader->findall(searchkey);
63 }
64 catch (const std::exception& e) {
65 g_log << Logger::Error << e.what() << endl;
66 throw PDNSException(e.what());
67 }
68
69 //Biggest item wins, so when we find something, we can jump out.
70 if (ret.size() > 0) {
71 break;
72 }
73 }
74
75 return ret;
76 }
77
78 TinyDNSBackend::TinyDNSBackend(const string& suffix)
79 {
80 setArgPrefix("tinydns" + suffix);
81 d_suffix = suffix;
82 d_locations = mustDo("locations");
83 d_ignorebogus = mustDo("ignore-bogus-records");
84 d_taiepoch = 4611686018427387904ULL + getArgAsNum("tai-adjust");
85 d_dnspacket = NULL;
86 d_cdbReader = NULL;
87 d_isAxfr = false;
88 d_isWildcardQuery = false;
89 }
90
91 void TinyDNSBackend::getUpdatedMasters(vector<DomainInfo>* retDomains)
92 {
93 auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
94 if (!domainInfo->count(d_suffix)) {
95 TDI_t tmp;
96 domainInfo->emplace(d_suffix, tmp);
97 }
98
99 TDI_t* domains = &(*domainInfo)[d_suffix];
100
101 vector<DomainInfo> allDomains;
102 getAllDomains(&allDomains, true, false);
103 if (domains->size() == 0 && !mustDo("notify-on-startup")) {
104 for (vector<DomainInfo>::iterator di = allDomains.begin(); di != allDomains.end(); ++di) {
105 di->notified_serial = 0;
106 }
107 }
108
109 for (vector<DomainInfo>::iterator di = allDomains.begin(); di != allDomains.end(); ++di) {
110 TDIByZone_t& zone_index = domains->get<tag_zone>();
111 TDIByZone_t::iterator itByZone = zone_index.find(di->zone);
112 if (itByZone == zone_index.end()) {
113 s_lastId++;
114
115 TinyDomainInfo tmp;
116 tmp.zone = di->zone;
117 tmp.id = s_lastId;
118 tmp.notified_serial = di->serial;
119 domains->insert(tmp);
120
121 di->id = s_lastId;
122 if (di->notified_serial > 0) {
123 retDomains->push_back(*di);
124 }
125 }
126 else {
127 if (itByZone->notified_serial < di->serial) {
128 di->id = itByZone->id;
129 retDomains->push_back(*di);
130 }
131 }
132 }
133 }
134
135 void TinyDNSBackend::setNotified(uint32_t id, uint32_t serial)
136 {
137 auto domainInfo = s_domainInfo.lock();
138 if (!domainInfo->count(d_suffix)) {
139 throw PDNSException("Can't get list of domains to set the serial.");
140 }
141 TDI_t* domains = &(*domainInfo)[d_suffix];
142 TDIById_t& domain_index = domains->get<tag_domainid>();
143 TDIById_t::iterator itById = domain_index.find(id);
144 if (itById == domain_index.end()) {
145 g_log << Logger::Error << backendname << "Received updated serial(" << serial << "), but domain ID (" << id << ") is not known in this backend." << endl;
146 }
147 else {
148 DLOG(g_log << Logger::Debug << backendname << "Setting serial for " << itById->zone << " to " << serial << endl);
149 domain_index.modify(itById, TDI_SerialModifier(serial));
150 }
151 (*domainInfo)[d_suffix] = *domains;
152 }
153
154 void TinyDNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
155 {
156 d_isAxfr = true;
157 d_isGetDomains = true;
158 d_dnspacket = NULL;
159
160 try {
161 d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
162 }
163 catch (const std::exception& e) {
164 g_log << Logger::Error << e.what() << endl;
165 throw PDNSException(e.what());
166 }
167
168 d_cdbReader->searchAll();
169 DNSResourceRecord rr;
170 std::unordered_set<DNSName> dupcheck;
171
172 while (get(rr)) {
173 if (rr.qtype.getCode() == QType::SOA && dupcheck.insert(rr.qname).second) {
174 DomainInfo di;
175 di.id = -1; //TODO: Check if this is ok.
176 di.backend = this;
177 di.zone = rr.qname;
178 di.kind = DomainInfo::Master;
179 di.last_check = time(0);
180
181 if (getSerial) {
182 SOAData sd;
183 try {
184 fillSOAData(rr.content, sd);
185 di.serial = sd.serial;
186 }
187 catch (...) {
188 di.serial = 0;
189 }
190 }
191
192 di.notified_serial = di.serial;
193 domains->push_back(di);
194 }
195 }
196 }
197
198 bool TinyDNSBackend::list(const DNSName& target, int domain_id, bool include_disabled)
199 {
200 d_isAxfr = true;
201 d_isGetDomains = false;
202 string key = target.toDNSStringLC();
203 try {
204 d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
205 }
206 catch (const std::exception& e) {
207 g_log << Logger::Error << e.what() << endl;
208 throw PDNSException(e.what());
209 }
210
211 return d_cdbReader->searchSuffix(key);
212 }
213
214 void TinyDNSBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p)
215 {
216 d_isAxfr = false;
217 d_isGetDomains = false;
218 string queryDomain = toLowerCanonic(qdomain.toString());
219
220 string key = simpleCompress(queryDomain);
221
222 DLOG(g_log << Logger::Debug << backendname << "[lookup] query for qtype [" << qtype.toString() << "] qdomain [" << qdomain << "]" << endl);
223 DLOG(g_log << Logger::Debug << "[lookup] key [" << makeHexDump(key) << "]" << endl);
224
225 d_isWildcardQuery = false;
226 if (key[0] == '\001' && key[1] == '\052') {
227 d_isWildcardQuery = true;
228 key.erase(0, 2);
229 }
230
231 d_qtype = qtype;
232
233 try {
234 d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
235 }
236 catch (const std::exception& e) {
237 g_log << Logger::Error << e.what() << endl;
238 throw PDNSException(e.what());
239 }
240
241 d_cdbReader->searchKey(key);
242 d_dnspacket = pkt_p;
243 }
244
245 bool TinyDNSBackend::get(DNSResourceRecord& rr)
246 {
247 pair<string, string> record;
248
249 while (d_cdbReader->readNext(record)) {
250 string val = record.second;
251 string key = record.first;
252
253 //DLOG(g_log<<Logger::Debug<<"[GET] Key: "<<makeHexDump(key)<<endl);
254 //DLOG(g_log<<Logger::Debug<<"[GET] Val: "<<makeHexDump(val)<<endl);
255 if (key[0] == '\000' && key[1] == '\045') { // skip locations
256 continue;
257 }
258
259 if (!d_isAxfr) {
260 // If we have a wildcard query, but the record we got is not a wildcard, we skip.
261 if (d_isWildcardQuery && val[2] != '\052' && val[2] != '\053') {
262 continue;
263 }
264
265 // If it is NOT a wildcard query, but we do find a wildcard record, we skip it.
266 if (!d_isWildcardQuery && (val[2] == '\052' || val[2] == '\053')) {
267 continue;
268 }
269 }
270
271 PacketReader pr(val, 0);
272 rr.qtype = QType(pr.get16BitInt());
273
274 if (d_isGetDomains && rr.qtype != QType::SOA) {
275 continue;
276 }
277
278 if (d_isAxfr || d_qtype.getCode() == QType::ANY || rr.qtype == d_qtype) {
279 char locwild = pr.get8BitInt();
280 if (locwild != '\075' && (locwild == '\076' || locwild == '\053')) {
281 if (d_isAxfr && d_locations) { // We skip records with a location in AXFR, unless we disable locations.
282 continue;
283 }
284 char recloc[2];
285 recloc[0] = pr.get8BitInt();
286 recloc[1] = pr.get8BitInt();
287
288 if (d_locations) {
289 bool foundLocation = false;
290 vector<string> locations = getLocations();
291 while (locations.size() > 0) {
292 string locId = locations.back();
293 locations.pop_back();
294
295 if (recloc[0] == locId[0] && recloc[1] == locId[1]) {
296 foundLocation = true;
297 break;
298 }
299 }
300 if (!foundLocation) {
301 continue;
302 }
303 }
304 }
305
306 if (d_isAxfr && (val[2] == '\052' || val[2] == '\053')) { // Keys are not stored with wildcard character, with AXFR we need to add that.
307 key.insert(0, 1, '\052');
308 key.insert(0, 1, '\001');
309 }
310 // rr.qname.clear();
311 rr.qname = DNSName(key.c_str(), key.size(), 0, false);
312 rr.domain_id = -1;
313 // 11:13.21 <@ahu> IT IS ALWAYS AUTH --- well not really because we are just a backend :-)
314 // We could actually do NSEC3-NARROW DNSSEC according to Habbie, if we do, we need to change something here.
315 rr.auth = true;
316
317 rr.ttl = pr.get32BitInt();
318 uint64_t timestamp = pr.get32BitInt();
319 timestamp <<= 32;
320 timestamp += pr.get32BitInt();
321 if (timestamp) {
322 uint64_t now = d_taiepoch + time(NULL);
323 if (rr.ttl == 0) {
324 if (timestamp < now) {
325 continue;
326 }
327 rr.ttl = timestamp - now;
328 if (rr.ttl <= 2)
329 rr.ttl = 2;
330 if (rr.ttl >= 3600)
331 rr.ttl = 3600;
332 }
333 else if (now <= timestamp) {
334 continue;
335 }
336 }
337 try {
338 DNSRecord dr;
339 dr.d_class = 1;
340 dr.d_type = rr.qtype.getCode();
341 dr.d_clen = val.size() - pr.getPosition();
342
343 auto drc = DNSRecordContent::mastermake(dr, pr);
344 rr.content = drc->getZoneRepresentation();
345 DLOG(cerr << "CONTENT: " << rr.content << endl);
346 }
347 catch (...) {
348 g_log << Logger::Error << backendname << "Failed to parse record content for " << rr.qname << " with type " << rr.qtype.toString();
349 if (d_ignorebogus || d_isGetDomains) {
350 g_log << ". Ignoring!" << endl;
351 continue;
352 }
353 else {
354 g_log << ". Erroring out!" << endl;
355 throw;
356 }
357 }
358 // DLOG(g_log<<Logger::Debug<<backendname<<"Returning ["<<rr.content<<"] for ["<<rr.qname<<"] of RecordType ["<<rr.qtype.toString()<<"]"<<endl;);
359 return true;
360 }
361 } // end of while
362 DLOG(g_log << Logger::Debug << backendname << "No more records to return." << endl);
363
364 d_cdbReader = nullptr;
365 return false;
366 }
367
368 // boilerplate
369 class TinyDNSFactory : public BackendFactory
370 {
371 public:
372 TinyDNSFactory() :
373 BackendFactory("tinydns") {}
374
375 void declareArguments(const string& suffix = "") override
376 {
377 declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the slave nameservers on startup. Default is no.", "no");
378 declare(suffix, "dbfile", "Location of the cdb data file", "data.cdb");
379 declare(suffix, "tai-adjust", "This adjusts the TAI value if timestamps are used. These seconds will be added to the start point (1970) and will allow you to adjust for leap seconds. The default is 11.", "11");
380 declare(suffix, "locations", "Enable or Disable location support in the backend. Changing the value to 'no' will make the backend ignore the locations. This then returns all records!", "yes");
381 declare(suffix, "ignore-bogus-records", "The data.cdb file might have some incorrect record data, this causes PowerDNS to fail, where tinydns would send out truncated data. This option makes powerdns ignore that data!", "no");
382 }
383
384 DNSBackend* make(const string& suffix = "") override
385 {
386 return new TinyDNSBackend(suffix);
387 }
388 };
389
390 // boilerplate
391 class TinyDNSLoader
392 {
393 public:
394 TinyDNSLoader()
395 {
396 BackendMakers().report(new TinyDNSFactory);
397 g_log << Logger::Info << "[tinydnsbackend] This is the tinydns backend version " VERSION
398 #ifndef REPRODUCIBLE
399 << " (" __DATE__ " " __TIME__ ")"
400 #endif
401 << " reporting" << endl;
402 }
403 };
404
405 static TinyDNSLoader tinydnsloader;