]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/geoipbackend/geoipbackend.cc
Merge pull request #9070 from rgacogne/boost-173
[thirdparty/pdns.git] / modules / geoipbackend / geoipbackend.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 "geoipbackend.hh"
26 #include "geoipinterface.hh"
27 #include "pdns/dns_random.hh"
28 #include <sstream>
29 #include <regex.h>
30 #include <glob.h>
31 #include <boost/algorithm/string/replace.hpp>
32 #include <fstream>
33 #include <yaml-cpp/yaml.h>
34
35 ReadWriteLock GeoIPBackend::s_state_lock;
36
37 struct GeoIPDNSResourceRecord: DNSResourceRecord {
38 int weight;
39 bool has_weight;
40 };
41
42 struct GeoIPService {
43 NetmaskTree<vector<string> > masks;
44 unsigned int netmask4;
45 unsigned int netmask6;
46 };
47
48 struct GeoIPDomain {
49 int id;
50 DNSName domain;
51 int ttl;
52 map<DNSName, GeoIPService> services;
53 map<DNSName, vector<GeoIPDNSResourceRecord> > records;
54 };
55
56 static vector<GeoIPDomain> s_domains;
57 static int s_rc = 0; // refcount - always accessed under lock
58
59 static string GeoIP_WEEKDAYS[] = { "mon", "tue", "wed", "thu", "fri", "sat", "sun" };
60 static string GeoIP_MONTHS[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };
61
62 /* So how does it work - we have static records and services. Static records "win".
63 We also insert empty non terminals for records and services.
64
65 If a service makes an internal reference to a domain also hosted within geoip, we give a direct
66 answers, no CNAMEs involved.
67
68 If the reference is external, we spoof up a CNAME, and good luck with that
69 */
70
71 GeoIPBackend::GeoIPBackend(const string& suffix) {
72 WriteLock wl(&s_state_lock);
73 d_dnssec = false;
74 setArgPrefix("geoip" + suffix);
75 if (getArg("dnssec-keydir").empty() == false) {
76 DIR *d = opendir(getArg("dnssec-keydir").c_str());
77 if (d == NULL) {
78 throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
79 }
80 d_dnssec = true;
81 closedir(d);
82 }
83 if (s_rc == 0) { // first instance gets to open everything
84 initialize();
85 }
86 s_rc++;
87 }
88
89 static vector<std::unique_ptr<GeoIPInterface> > s_geoip_files;
90
91 string getGeoForLua(const std::string& ip, int qaint);
92 static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl);
93
94 void GeoIPBackend::initialize() {
95 YAML::Node config;
96 vector<GeoIPDomain> tmp_domains;
97
98 s_geoip_files.clear(); // reset pointers
99
100 if (getArg("database-files").empty() == false) {
101 vector<string> files;
102 stringtok(files, getArg("database-files"), " ,\t\r\n");
103 for(auto const& file: files) {
104 s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
105 }
106 }
107
108 if (s_geoip_files.empty())
109 g_log<<Logger::Warning<<"No GeoIP database files loaded!"<<endl;
110
111 if(!getArg("zones-file").empty()) {
112 try {
113 config = YAML::LoadFile(getArg("zones-file"));
114 } catch (YAML::Exception &ex) {
115 throw PDNSException(string("Cannot read config file ") + ex.msg);
116 }
117 }
118
119 for(YAML::Node domain : config["domains"]) {
120 GeoIPDomain dom;
121 dom.id = tmp_domains.size();
122 dom.domain = DNSName(domain["domain"].as<string>());
123 dom.ttl = domain["ttl"].as<int>();
124
125 for(YAML::const_iterator recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
126 DNSName qname = DNSName(recs->first.as<string>());
127 vector<GeoIPDNSResourceRecord> rrs;
128
129 for(YAML::Node item : recs->second) {
130 YAML::const_iterator rec = item.begin();
131 GeoIPDNSResourceRecord rr;
132 rr.domain_id = dom.id;
133 rr.ttl = dom.ttl;
134 rr.qname = qname;
135 if (rec->first.IsNull()) {
136 rr.qtype = QType(0);
137 } else {
138 string qtype = boost::to_upper_copy(rec->first.as<string>());
139 rr.qtype = qtype;
140 }
141 rr.has_weight = false;
142 rr.weight = 100;
143 if (rec->second.IsNull()) {
144 rr.content = "";
145 } else if (rec->second.IsMap()) {
146 for(YAML::const_iterator iter = rec->second.begin(); iter != rec->second.end(); iter++) {
147 string attr = iter->first.as<string>();
148 if (attr == "content") {
149 string content = iter->second.as<string>();
150 rr.content = content;
151 } else if (attr == "weight") {
152 rr.weight = iter->second.as<int>();
153 if (rr.weight <= 0) {
154 g_log<<Logger::Error<<"Weight must be positive for " << rr.qname << endl;
155 throw PDNSException(string("Weight must be positive for ") + rr.qname.toLogString());
156 }
157 rr.has_weight = true;
158 } else if (attr == "ttl") {
159 rr.ttl = iter->second.as<int>();
160 } else {
161 g_log<<Logger::Error<<"Unsupported record attribute " << attr << " for " << rr.qname << endl;
162 throw PDNSException(string("Unsupported record attribute ") + attr + string(" for ") + rr.qname.toLogString());
163 }
164 }
165 } else {
166 string content=rec->second.as<string>();
167 rr.content = content;
168 rr.weight = 100;
169 }
170 rr.auth = 1;
171 rrs.push_back(rr);
172 }
173 std::swap(dom.records[qname], rrs);
174 }
175
176 for(YAML::const_iterator service = domain["services"].begin(); service != domain["services"].end(); service++) {
177 unsigned int netmask4 = 0, netmask6 = 0;
178 DNSName srvName{service->first.as<string>()};
179 NetmaskTree<vector<string> > nmt;
180
181 // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
182 if (service->second.IsMap()) {
183 for(YAML::const_iterator net = service->second.begin(); net != service->second.end(); net++) {
184 vector<string> value;
185 if (net->second.IsSequence()) {
186 value = net->second.as<vector<string> >();
187 } else {
188 value.push_back(net->second.as<string>());
189 }
190 if (net->first.as<string>() == "default") {
191 nmt.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(),value.end());
192 nmt.insert(Netmask("::/0")).second.swap(value);
193 } else {
194 Netmask nm{net->first.as<string>()};
195 nmt.insert(nm).second.swap(value);
196 if (nm.isIPv6() == true && netmask6 < nm.getBits())
197 netmask6 = nm.getBits();
198 if (nm.isIPv6() == false && netmask4 < nm.getBits())
199 netmask4 = nm.getBits();
200 }
201 }
202 } else {
203 vector<string> value;
204 if (service->second.IsSequence()) {
205 value = service->second.as<vector<string> >();
206 } else {
207 value.push_back(service->second.as<string>());
208 }
209 nmt.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(),value.end());
210 nmt.insert(Netmask("::/0")).second.swap(value);
211 }
212
213 dom.services[srvName].netmask4 = netmask4;
214 dom.services[srvName].netmask6 = netmask6;
215 dom.services[srvName].masks.swap(nmt);
216 }
217
218 // rectify the zone, first static records
219 for(auto &item : dom.records) {
220 // ensure we have parent in records
221 DNSName name = item.first;
222 while(name.chopOff() && name.isPartOf(dom.domain)) {
223 if (dom.records.find(name) == dom.records.end() && !dom.services.count(name)) { // don't ENT out a service!
224 GeoIPDNSResourceRecord rr;
225 vector<GeoIPDNSResourceRecord> rrs;
226 rr.domain_id = dom.id;
227 rr.ttl = dom.ttl;
228 rr.qname = name;
229 rr.qtype = QType(0); // empty non terminal
230 rr.content = "";
231 rr.auth = 1;
232 rr.weight = 100;
233 rr.has_weight = false;
234 rrs.push_back(rr);
235 std::swap(dom.records[name], rrs);
236 }
237 }
238 }
239
240 // then services
241 for(auto &item : dom.services) {
242 // ensure we have parent in records
243 DNSName name = item.first;
244 while(name.chopOff() && name.isPartOf(dom.domain)) {
245 if (dom.records.find(name) == dom.records.end()) {
246 GeoIPDNSResourceRecord rr;
247 vector<GeoIPDNSResourceRecord> rrs;
248 rr.domain_id = dom.id;
249 rr.ttl = dom.ttl;
250 rr.qname = name;
251 rr.qtype = QType(0);
252 rr.content = "";
253 rr.auth = 1;
254 rr.weight = 100;
255 rr.has_weight = false;
256 rrs.push_back(rr);
257 std::swap(dom.records[name], rrs);
258 }
259 }
260 }
261
262 // finally fix weights
263 for(auto &item: dom.records) {
264 map<uint16_t, float> weights;
265 map<uint16_t, float> sums;
266 map<uint16_t, GeoIPDNSResourceRecord> lasts;
267 bool has_weight=false;
268 // first we look for used weight
269 for(const auto &rr: item.second) {
270 weights[rr.qtype.getCode()] += rr.weight;
271 if (rr.has_weight) has_weight = true;
272 }
273 if (has_weight) {
274 // put them back as probabilities and values..
275 for(auto &rr: item.second) {
276 uint16_t rr_type = rr.qtype.getCode();
277 rr.weight=static_cast<int>((static_cast<float>(rr.weight) / weights[rr_type])*1000.0);
278 sums[rr_type] += rr.weight;
279 rr.has_weight = has_weight;
280 lasts[rr_type] = rr;
281 }
282 // remove rounding gap
283 for(auto &x: lasts) {
284 float sum = sums[x.first];
285 if (sum < 1000)
286 x.second.weight += (1000-sum);
287 }
288 }
289 }
290
291 tmp_domains.push_back(std::move(dom));
292 }
293
294 s_domains.clear();
295 std::swap(s_domains, tmp_domains);
296
297 extern std::function<std::string(const std::string& ip, int)> g_getGeo;
298 g_getGeo = getGeoForLua;
299 }
300
301 GeoIPBackend::~GeoIPBackend() {
302 try {
303 WriteLock wl(&s_state_lock);
304 s_rc--;
305 if (s_rc == 0) { // last instance gets to cleanup
306 s_geoip_files.clear();
307 s_domains.clear();
308 }
309 }
310 catch(...) {
311 }
312 }
313
314 bool GeoIPBackend::lookup_static(const GeoIPDomain &dom, const DNSName &search, const QType &qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask &gl) {
315 const auto& i = dom.records.find(search);
316 map<uint16_t,int> cumul_probabilities;
317 int probability_rnd = 1+(dns_random(1000)); // setting probability=0 means it never is used
318
319 if (i != dom.records.end()) { // return static value
320 for(const auto& rr : i->second) {
321 if (qtype != QType::ANY && rr.qtype != qtype) continue;
322
323 if (rr.has_weight) {
324 gl.netmask = (addr.isIPv6()?128:32);
325 int comp = cumul_probabilities[rr.qtype.getCode()];
326 cumul_probabilities[rr.qtype.getCode()] += rr.weight;
327 if (rr.weight == 0 || probability_rnd < comp || probability_rnd > (comp + rr.weight))
328 continue;
329 }
330 const string& content = format2str(rr.content, addr, gl);
331 if (rr.qtype != QType::ENT && rr.qtype != QType::TXT && content.empty()) continue;
332 d_result.push_back(rr);
333 d_result.back().content = content;
334 d_result.back().qname = qdomain;
335 }
336 // ensure we get most strict netmask
337 for(DNSResourceRecord& rr: d_result) {
338 rr.scopeMask = gl.netmask;
339 }
340 return true; // no need to go further
341 }
342
343 return false;
344 };
345
346 void GeoIPBackend::lookup(const QType &qtype, const DNSName& qdomain, int zoneId, DNSPacket *pkt_p) {
347 ReadLock rl(&s_state_lock);
348 const GeoIPDomain* dom;
349 GeoIPNetmask gl;
350 bool found = false;
351
352 if (d_result.size()>0)
353 throw PDNSException("Cannot perform lookup while another is running");
354
355 d_result.clear();
356
357 if (zoneId > -1 && zoneId < static_cast<int>(s_domains.size()))
358 dom = &(s_domains[zoneId]);
359 else {
360 for(const GeoIPDomain& i : s_domains) { // this is arguably wrong, we should probably find the most specific match
361 if (qdomain.isPartOf(i.domain)) {
362 dom = &i;
363 found = true;
364 break;
365 }
366 }
367 if (!found) return; // not found
368 }
369
370 Netmask addr{"0.0.0.0/0"};
371 if (pkt_p != NULL)
372 addr = Netmask(pkt_p->getRealRemote());
373
374 gl.netmask = 0;
375
376 (void)this->lookup_static(*dom, qdomain, qtype, qdomain, addr, gl);
377
378 const auto& target = (*dom).services.find(qdomain);
379 if (target == (*dom).services.end()) return; // no hit
380
381 const NetmaskTree<vector<string> >::node_type* node = target->second.masks.lookup(addr);
382 if (node == NULL) return; // no hit, again.
383
384 DNSName sformat;
385 gl.netmask = node->first.getBits();
386 // figure out smallest sensible netmask
387 if (gl.netmask == 0) {
388 GeoIPNetmask tmp_gl;
389 tmp_gl.netmask = 0;
390 // get netmask from geoip backend
391 if (queryGeoIP(addr, GeoIPInterface::Name, tmp_gl) == "unknown") {
392 if (addr.isIPv6())
393 gl.netmask = target->second.netmask6;
394 else
395 gl.netmask = target->second.netmask4;
396 }
397 } else {
398 if (addr.isIPv6())
399 gl.netmask = target->second.netmask6;
400 else
401 gl.netmask = target->second.netmask4;
402 }
403
404 // note that this means the array format won't work with indirect
405 for(auto it = node->second.begin(); it != node->second.end(); it++) {
406 sformat = DNSName(format2str(*it, addr, gl));
407
408 // see if the record can be found
409 if (this->lookup_static((*dom), sformat, qtype, qdomain, addr, gl))
410 return;
411 }
412
413 if (!d_result.empty()) {
414 g_log<<Logger::Error<<
415 "Cannot have static record and CNAME at the same time." <<
416 "Please fix your configuration for \"" << qdomain << "\", so that " <<
417 "it can be resolved by GeoIP backend directly."<< std::endl;
418 d_result.clear();
419 return;
420 }
421
422 // we need this line since we otherwise claim to have NS records etc
423 if (!(qtype == QType::ANY || qtype == QType::CNAME)) return;
424
425 DNSResourceRecord rr;
426 rr.domain_id = dom->id;
427 rr.qtype = QType::CNAME;
428 rr.qname = qdomain;
429 rr.content = sformat.toString();
430 rr.auth = 1;
431 rr.ttl = dom->ttl;
432 rr.scopeMask = gl.netmask;
433 d_result.push_back(rr);
434 }
435
436 bool GeoIPBackend::get(DNSResourceRecord &r) {
437 if (d_result.empty()) return false;
438
439 r = d_result.back();
440 d_result.pop_back();
441
442 return true;
443 }
444
445 static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl) {
446 string ret = "unknown";
447
448 for(auto const& gi: s_geoip_files) {
449 string val;
450 const string ip = addr.toStringNoMask();
451 bool found = false;
452
453 switch(attribute) {
454 case GeoIPInterface::ASn:
455 if (addr.isIPv6()) found = gi->queryASnumV6(val, gl, ip);
456 else found =gi->queryASnum(val, gl, ip);
457 break;
458 case GeoIPInterface::Name:
459 if (addr.isIPv6()) found = gi->queryNameV6(val, gl, ip);
460 else found = gi->queryName(val, gl, ip);
461 break;
462 case GeoIPInterface::Continent:
463 if (addr.isIPv6()) found = gi->queryContinentV6(val, gl, ip);
464 else found = gi->queryContinent(val, gl, ip);
465 break;
466 case GeoIPInterface::Region:
467 if (addr.isIPv6()) found = gi->queryRegionV6(val, gl, ip);
468 else found = gi->queryRegion(val, gl, ip);
469 break;
470 case GeoIPInterface::Country:
471 if (addr.isIPv6()) found = gi->queryCountryV6(val, gl, ip);
472 else found = gi->queryCountry(val, gl, ip);
473 break;
474 case GeoIPInterface::Country2:
475 if (addr.isIPv6()) found = gi->queryCountry2V6(val, gl, ip);
476 else found = gi->queryCountry2(val, gl, ip);
477 break;
478 case GeoIPInterface::City:
479 if (addr.isIPv6()) found = gi->queryCityV6(val, gl, ip);
480 else found = gi->queryCity(val, gl, ip);
481 break;
482 case GeoIPInterface::Location:
483 double lat=0, lon=0;
484 boost::optional<int> alt, prec;
485 if (addr.isIPv6()) found = gi->queryLocationV6(gl, ip, lat, lon, alt, prec);
486 else found = gi->queryLocation(gl, ip, lat, lon, alt, prec);
487 val = std::to_string(lat)+" "+std::to_string(lon);
488 break;
489 }
490
491 if (!found || val.empty() || val == "--") continue; // try next database
492 ret = val;
493 std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
494 break;
495 }
496
497 if (ret == "unknown") gl.netmask = (addr.isIPv6()?128:32); // prevent caching
498 return ret;
499 }
500
501 string getGeoForLua(const std::string& ip, int qaint)
502 {
503 GeoIPInterface::GeoIPQueryAttribute qa((GeoIPInterface::GeoIPQueryAttribute)qaint);
504 try {
505 const Netmask addr{ip};
506 GeoIPNetmask gl;
507 string res=queryGeoIP(addr, qa, gl);
508 // cout<<"Result for "<<ip<<" lookup: "<<res<<endl;
509 if(qa==GeoIPInterface::ASn && boost::starts_with(res, "as"))
510 return res.substr(2);
511 return res;
512 }
513 catch(std::exception& e) {
514 cout<<"Error: "<<e.what()<<endl;
515 }
516 catch(PDNSException& e) {
517 cout<<"Error: "<<e.reason<<endl;
518 }
519 return "";
520 }
521
522 static bool queryGeoLocation(const Netmask& addr, GeoIPNetmask& gl, double& lat, double& lon,
523 boost::optional<int>& alt, boost::optional<int>& prec)
524 {
525 for(auto const& gi: s_geoip_files) {
526 string val;
527 if (addr.isIPv6()) {
528 if (gi->queryLocationV6(gl, addr.toStringNoMask(), lat, lon, alt, prec))
529 return true;
530 } else if (gi->queryLocation(gl, addr.toStringNoMask(), lat, lon, alt, prec))
531 return true;
532 }
533 return false;
534 }
535
536 string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmask& gl) {
537 string::size_type cur,last;
538 boost::optional<int> alt, prec;
539 double lat, lon;
540 time_t t = time((time_t*)NULL);
541 GeoIPNetmask tmp_gl; // largest wins
542 struct tm gtm;
543 gmtime_r(&t, &gtm);
544 last=0;
545
546 while((cur = sformat.find("%", last)) != string::npos) {
547 string rep;
548 int nrep=3;
549 tmp_gl.netmask = 0;
550 if (!sformat.compare(cur,3,"%cn")) {
551 rep = queryGeoIP(addr, GeoIPInterface::Continent, tmp_gl);
552 } else if (!sformat.compare(cur,3,"%co")) {
553 rep = queryGeoIP(addr, GeoIPInterface::Country, tmp_gl);
554 } else if (!sformat.compare(cur,3,"%cc")) {
555 rep = queryGeoIP(addr, GeoIPInterface::Country2, tmp_gl);
556 } else if (!sformat.compare(cur,3,"%af")) {
557 rep = (addr.isIPv6()?"v6":"v4");
558 } else if (!sformat.compare(cur,3,"%as")) {
559 rep = queryGeoIP(addr, GeoIPInterface::ASn, tmp_gl);
560 } else if (!sformat.compare(cur,3,"%re")) {
561 rep = queryGeoIP(addr, GeoIPInterface::Region, tmp_gl);
562 } else if (!sformat.compare(cur,3,"%na")) {
563 rep = queryGeoIP(addr, GeoIPInterface::Name, tmp_gl);
564 } else if (!sformat.compare(cur,3,"%ci")) {
565 rep = queryGeoIP(addr, GeoIPInterface::City, tmp_gl);
566 } else if (!sformat.compare(cur,4,"%loc")) {
567 char ns, ew;
568 int d1, d2, m1, m2;
569 double s1, s2;
570 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
571 rep = "";
572 } else {
573 ns = (lat>0) ? 'N' : 'S';
574 ew = (lon>0) ? 'E' : 'W';
575 /* remove sign */
576 lat = fabs(lat);
577 lon = fabs(lon);
578 d1 = static_cast<int>(lat);
579 d2 = static_cast<int>(lon);
580 m1 = static_cast<int>((lat - d1)*60.0);
581 m2 = static_cast<int>((lon - d2)*60.0);
582 s1 = static_cast<double>(lat - d1 - m1/60.0)*3600.0;
583 s2 = static_cast<double>(lon - d2 - m2/60.0)*3600.0;
584 rep = str(boost::format("%d %d %0.3f %c %d %d %0.3f %c") %
585 d1 % m1 % s1 % ns % d2 % m2 % s2 % ew);
586 if (alt)
587 rep = rep + str(boost::format(" %d.00") % *alt);
588 else
589 rep = rep + string(" 0.00");
590 if (prec)
591 rep = rep + str(boost::format(" %dm") % *prec);
592 }
593 nrep = 4;
594 } else if (!sformat.compare(cur,4,"%lat")) {
595 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
596 rep = "";
597 } else {
598 rep = str(boost::format("%lf") % lat);
599 }
600 nrep = 4;
601 } else if (!sformat.compare(cur,4,"%lon")) {
602 if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
603 rep = "";
604 } else {
605 rep = str(boost::format("%lf") % lon);
606 }
607 nrep = 4;
608 } else if (!sformat.compare(cur,3,"%hh")) {
609 rep = boost::str(boost::format("%02d") % gtm.tm_hour);
610 tmp_gl.netmask = (addr.isIPv6()?128:32);
611 } else if (!sformat.compare(cur,3,"%yy")) {
612 rep = boost::str(boost::format("%02d") % (gtm.tm_year + 1900));
613 tmp_gl.netmask = (addr.isIPv6()?128:32);
614 } else if (!sformat.compare(cur,3,"%dd")) {
615 rep = boost::str(boost::format("%02d") % (gtm.tm_yday + 1));
616 tmp_gl.netmask = (addr.isIPv6()?128:32);
617 } else if (!sformat.compare(cur,4,"%wds")) {
618 nrep=4;
619 rep = GeoIP_WEEKDAYS[gtm.tm_wday];
620 tmp_gl.netmask = (addr.isIPv6()?128:32);
621 } else if (!sformat.compare(cur,4,"%mos")) {
622 nrep=4;
623 rep = GeoIP_MONTHS[gtm.tm_mon];
624 tmp_gl.netmask = (addr.isIPv6()?128:32);
625 } else if (!sformat.compare(cur,3,"%wd")) {
626 rep = boost::str(boost::format("%02d") % (gtm.tm_wday + 1));
627 tmp_gl.netmask = (addr.isIPv6()?128:32);
628 } else if (!sformat.compare(cur,3,"%mo")) {
629 rep = boost::str(boost::format("%02d") % (gtm.tm_mon + 1));
630 tmp_gl.netmask = (addr.isIPv6()?128:32);
631 } else if (!sformat.compare(cur,4,"%ip6")) {
632 nrep = 4;
633 if (addr.isIPv6())
634 rep = addr.toStringNoMask();
635 else
636 rep = "";
637 tmp_gl.netmask = (addr.isIPv6()?128:32);
638 } else if (!sformat.compare(cur,4,"%ip4")) {
639 nrep = 4;
640 if (!addr.isIPv6())
641 rep = addr.toStringNoMask();
642 else
643 rep = "";
644 tmp_gl.netmask = (addr.isIPv6()?128:32);
645 } else if (!sformat.compare(cur,3,"%ip")) {
646 rep = addr.toStringNoMask();
647 tmp_gl.netmask = (addr.isIPv6()?128:32);
648 } else if (!sformat.compare(cur,2,"%%")) {
649 last = cur + 2; continue;
650 } else {
651 last = cur + 1; continue;
652 }
653 if (tmp_gl.netmask > gl.netmask) gl.netmask = tmp_gl.netmask;
654 sformat.replace(cur, nrep, rep);
655 last = cur + rep.size(); // move to next attribute
656 }
657 return sformat;
658 }
659
660 void GeoIPBackend::reload() {
661 WriteLock wl(&s_state_lock);
662
663 try {
664 initialize();
665 } catch (PDNSException &pex) {
666 g_log<<Logger::Error<<"GeoIP backend reload failed: " << pex.reason << endl;
667 } catch (std::exception &stex) {
668 g_log<<Logger::Error<<"GeoIP backend reload failed: " << stex.what() << endl;
669 } catch (...) {
670 g_log<<Logger::Error<<"GeoIP backend reload failed" << endl;
671 }
672 }
673
674 void GeoIPBackend::rediscover(string* status) {
675 reload();
676 }
677
678 bool GeoIPBackend::getDomainInfo(const DNSName& domain, DomainInfo &di, bool getSerial) {
679 ReadLock rl(&s_state_lock);
680
681 for(GeoIPDomain dom : s_domains) {
682 if (dom.domain == domain) {
683 SOAData sd;
684 this->getSOA(domain, sd);
685 di.id = dom.id;
686 di.zone = dom.domain;
687 di.serial = sd.serial;
688 di.kind = DomainInfo::Native;
689 di.backend = this;
690 return true;
691 }
692 }
693 return false;
694 }
695
696 bool GeoIPBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta) {
697 if (!d_dnssec) return false;
698
699 ReadLock rl(&s_state_lock);
700 for(GeoIPDomain dom : s_domains) {
701 if (dom.domain == name) {
702 if (hasDNSSECkey(dom.domain)) {
703 meta[string("NSEC3NARROW")].push_back("1");
704 meta[string("NSEC3PARAM")].push_back("1 0 1 f95a");
705 }
706 return true;
707 }
708 }
709 return false;
710 }
711
712 bool GeoIPBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) {
713 if (!d_dnssec) return false;
714
715 ReadLock rl(&s_state_lock);
716 for(GeoIPDomain dom : s_domains) {
717 if (dom.domain == name) {
718 if (hasDNSSECkey(dom.domain)) {
719 if (kind == "NSEC3NARROW")
720 meta.push_back(string("1"));
721 if (kind == "NSEC3PARAM")
722 meta.push_back(string("1 0 1 f95a"));
723 }
724 return true;
725 }
726 }
727 return false;
728 }
729
730 bool GeoIPBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys) {
731 if (!d_dnssec) return false;
732 ReadLock rl(&s_state_lock);
733 for(GeoIPDomain dom : s_domains) {
734 if (dom.domain == name) {
735 regex_t reg;
736 regmatch_t regm[5];
737 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
738 ostringstream pathname;
739 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
740 glob_t glob_result;
741 if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
742 for(size_t i=0;i<glob_result.gl_pathc;i++) {
743 if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
744 DNSBackend::KeyData kd;
745 kd.id = pdns_stou(glob_result.gl_pathv[i]+regm[3].rm_so);
746 kd.active = !strncmp(glob_result.gl_pathv[i]+regm[4].rm_so, "1", 1);
747 kd.published = true;
748 kd.flags = pdns_stou(glob_result.gl_pathv[i]+regm[2].rm_so);
749 ifstream ifs(glob_result.gl_pathv[i]);
750 ostringstream content;
751 char buffer[1024];
752 while(ifs.good()) {
753 ifs.read(buffer, sizeof buffer);
754 if (ifs.gcount()>0) {
755 content << string(buffer, ifs.gcount());
756 }
757 }
758 ifs.close();
759 kd.content = content.str();
760 keys.push_back(kd);
761 }
762 }
763 }
764 regfree(&reg);
765 globfree(&glob_result);
766 return true;
767 }
768 }
769 return false;
770 }
771
772 bool GeoIPBackend::removeDomainKey(const DNSName& name, unsigned int id) {
773 if (!d_dnssec) return false;
774 WriteLock rl(&s_state_lock);
775 ostringstream path;
776
777 for(GeoIPDomain dom : s_domains) {
778 if (dom.domain == name) {
779 regex_t reg;
780 regmatch_t regm[5];
781 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
782 ostringstream pathname;
783 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
784 glob_t glob_result;
785 if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
786 for(size_t i=0;i<glob_result.gl_pathc;i++) {
787 if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
788 unsigned int kid = pdns_stou(glob_result.gl_pathv[i]+regm[3].rm_so);
789 if (kid == id) {
790 if (unlink(glob_result.gl_pathv[i])) {
791 cerr << "Cannot delete key:" << strerror(errno) << endl;
792 }
793 break;
794 }
795 }
796 }
797 }
798 regfree(&reg);
799 globfree(&glob_result);
800 return true;
801 }
802 }
803 return false;
804 }
805
806 bool GeoIPBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id) {
807 if (!d_dnssec) return false;
808 WriteLock rl(&s_state_lock);
809 unsigned int nextid=1;
810
811 for(GeoIPDomain dom : s_domains) {
812 if (dom.domain == name) {
813 regex_t reg;
814 regmatch_t regm[5];
815 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
816 ostringstream pathname;
817 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
818 glob_t glob_result;
819 if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
820 for(size_t i=0;i<glob_result.gl_pathc;i++) {
821 if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
822 unsigned int kid = pdns_stou(glob_result.gl_pathv[i]+regm[3].rm_so);
823 if (kid >= nextid) nextid = kid+1;
824 }
825 }
826 }
827 regfree(&reg);
828 globfree(&glob_result);
829 pathname.str("");
830 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "." << key.flags << "." << nextid << "." << (key.active?"1":"0") << ".key";
831 ofstream ofs(pathname.str().c_str());
832 ofs.write(key.content.c_str(), key.content.size());
833 ofs.close();
834 id = nextid;
835 return true;
836 }
837 }
838 return false;
839
840 }
841
842 bool GeoIPBackend::activateDomainKey(const DNSName& name, unsigned int id) {
843 if (!d_dnssec) return false;
844 WriteLock rl(&s_state_lock);
845 for(GeoIPDomain dom : s_domains) {
846 if (dom.domain == name) {
847 regex_t reg;
848 regmatch_t regm[5];
849 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
850 ostringstream pathname;
851 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
852 glob_t glob_result;
853 if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
854 for(size_t i=0;i<glob_result.gl_pathc;i++) {
855 if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
856 unsigned int kid = pdns_stou(glob_result.gl_pathv[i]+regm[3].rm_so);
857 if (kid == id && !strcmp(glob_result.gl_pathv[i]+regm[4].rm_so,"0")) {
858 ostringstream newpath;
859 newpath << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "." << pdns_stou(glob_result.gl_pathv[i]+regm[2].rm_so) << "." << kid << ".1.key";
860 if (rename(glob_result.gl_pathv[i], newpath.str().c_str())) {
861 cerr << "Cannot activate key: " << strerror(errno) << endl;
862 }
863 }
864 }
865 }
866 }
867 globfree(&glob_result);
868 regfree(&reg);
869 return true;
870 }
871 }
872 return false;
873 }
874
875 bool GeoIPBackend::deactivateDomainKey(const DNSName& name, unsigned int id) {
876 if (!d_dnssec) return false;
877 WriteLock rl(&s_state_lock);
878 for(GeoIPDomain dom : s_domains) {
879 if (dom.domain == name) {
880 regex_t reg;
881 regmatch_t regm[5];
882 regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE|REG_EXTENDED);
883 ostringstream pathname;
884 pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
885 glob_t glob_result;
886 if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
887 for(size_t i=0;i<glob_result.gl_pathc;i++) {
888 if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
889 unsigned int kid = pdns_stou(glob_result.gl_pathv[i]+regm[3].rm_so);
890 if (kid == id && !strcmp(glob_result.gl_pathv[i]+regm[4].rm_so,"1")) {
891 ostringstream newpath;
892 newpath << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "." << pdns_stou(glob_result.gl_pathv[i]+regm[2].rm_so) << "." << kid << ".0.key";
893 if (rename(glob_result.gl_pathv[i], newpath.str().c_str())) {
894 cerr << "Cannot deactivate key: " << strerror(errno) << endl;
895 }
896 }
897 }
898 }
899 }
900 globfree(&glob_result);
901 regfree(&reg);
902 return true;
903 }
904 }
905 return false;
906 }
907
908 bool GeoIPBackend::publishDomainKey(const DNSName& name, unsigned int id) {
909 return false;
910 }
911
912 bool GeoIPBackend::unpublishDomainKey(const DNSName& name, unsigned int id) {
913 return false;
914 }
915
916
917 bool GeoIPBackend::hasDNSSECkey(const DNSName& name) {
918 ostringstream pathname;
919 pathname << getArg("dnssec-keydir") << "/" << name.toStringNoDot() << "*.key";
920 glob_t glob_result;
921 if (glob(pathname.str().c_str(),GLOB_ERR,NULL,&glob_result) == 0) {
922 globfree(&glob_result);
923 return true;
924 }
925 return false;
926 }
927
928 class GeoIPFactory : public BackendFactory{
929 public:
930 GeoIPFactory() : BackendFactory("geoip") {}
931
932 void declareArguments(const string &suffix = "") {
933 declare(suffix, "zones-file", "YAML file to load zone(s) configuration", "");
934 declare(suffix, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
935 declare(suffix, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
936 }
937
938 DNSBackend *make(const string &suffix) {
939 return new GeoIPBackend(suffix);
940 }
941 };
942
943 class GeoIPLoader {
944 public:
945 GeoIPLoader() {
946 BackendMakers().report(new GeoIPFactory);
947 g_log << Logger::Info << "[geoipbackend] This is the geoip backend version " VERSION
948 #ifndef REPRODUCIBLE
949 << " (" __DATE__ " " __TIME__ ")"
950 #endif
951 << " reporting" << endl;
952 }
953 };
954
955 static GeoIPLoader geoiploader;