• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 19332892815

13 Nov 2025 01:18PM UTC coverage: 73.029% (-0.04%) from 73.072%
19332892815

Pull #16389

github

web-flow
Merge bd30b08e1 into 60476c10b
Pull Request #16389: auth Lua health checks: more responsiveness

38533 of 63446 branches covered (60.73%)

Branch coverage included in aggregate %.

0 of 13 new or added lines in 1 file covered. (0.0%)

175 existing lines in 14 files now uncovered.

128003 of 164594 relevant lines covered (77.77%)

6070740.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.22
/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
#include <cstdint>
23
#ifdef HAVE_CONFIG_H
24
#include "config.h"
25
#endif
26
#include "geoipbackend.hh"
27
#include "geoipinterface.hh"
28
#include "pdns/dns_random.hh"
29
#include <sstream>
30
#include <regex.h>
31
#include <glob.h>
32
#include <boost/algorithm/string/replace.hpp>
33
#include <boost/format.hpp>
34
#include <fstream>
35
#include <filesystem>
36
#include <utility>
37
#include <cmath>
38
#pragma GCC diagnostic push
39
#pragma GCC diagnostic ignored "-Wshadow"
40
#include <yaml-cpp/yaml.h>
41
#pragma GCC diagnostic pop
42

43
ReadWriteLock GeoIPBackend::s_state_lock;
44

45
struct GeoIPDNSResourceRecord : DNSResourceRecord
46
{
47
  int weight{};
48
  bool has_weight{};
49
};
50

51
struct GeoIPService
52
{
53
  NetmaskTree<vector<string>> masks;
54
  unsigned int netmask4;
55
  unsigned int netmask6;
56
};
57

58
struct GeoIPDomain
59
{
60
  domainid_t id{};
61
  ZoneName domain;
62
  int ttl{};
63
  map<DNSName, GeoIPService> services;
64
  map<DNSName, vector<GeoIPDNSResourceRecord>> records;
65
  vector<string> mapping_lookup_formats;
66
  map<std::string, std::string> custom_mapping;
67
};
68

69
static vector<GeoIPDomain> s_domains;
70
static int s_rc = 0; // refcount - always accessed under lock
71

72
const static std::array<string, 7> GeoIP_WEEKDAYS = {"mon", "tue", "wed", "thu", "fri", "sat", "sun"};
73
const static std::array<string, 12> GeoIP_MONTHS = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
74

75
/* So how does it work - we have static records and services. Static records "win".
76
   We also insert empty non terminals for records and services.
77

78
   If a service makes an internal reference to a domain also hosted within geoip, we give a direct
79
   answers, no CNAMEs involved.
80

81
   If the reference is external, we spoof up a CNAME, and good luck with that
82
*/
83

84
struct DirPtrDeleter
85
{
86
  /* using a deleter instead of decltype(&closedir) has two big advantages:
87
     - the deleter is included in the type and does not have to be passed
88
       when creating a new object (easier to use, less memory usage, in theory
89
       better inlining)
90
     - we avoid the annoying "ignoring attributes on template argument ‘int (*)(DIR*)’"
91
       warning from the compiler, which is there because closedir is tagged as __nonnull((1))
92
  */
93
  void operator()(DIR* dirPtr) const noexcept
94
  {
6✔
95
    closedir(dirPtr);
6✔
96
  }
6✔
97
};
98

99
using UniqueDirPtr = std::unique_ptr<DIR, DirPtrDeleter>;
100

101
GeoIPBackend::GeoIPBackend(const string& suffix)
102
{
12✔
103
  WriteLock writeLock(&s_state_lock);
12✔
104
  setArgPrefix("geoip" + suffix);
12✔
105
  if (!getArg("dnssec-keydir").empty()) {
12✔
106
    auto dirHandle = UniqueDirPtr(opendir(getArg("dnssec-keydir").c_str()));
6✔
107
    if (!dirHandle) {
6!
108
      throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
×
109
    }
×
110
    d_dnssec = true;
6✔
111
  }
6✔
112
  if (s_rc == 0) { // first instance gets to open everything
12✔
113
    initialize();
7✔
114
  }
7✔
115
  s_rc++;
12✔
116
}
12✔
117

118
static vector<std::unique_ptr<GeoIPInterface>> s_geoip_files;
119

120
string getGeoForLua(const std::string& ip, int qaint);
121
static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl);
122

123
// validateMappingLookupFormats validates any custom format provided by the
124
// user does not use the custom mapping placeholder again, else it would do an
125
// infinite recursion.
126
static bool validateMappingLookupFormats(const vector<string>& formats)
127
{
7✔
128
  string::size_type cur = 0;
7✔
129
  string::size_type last = 0;
7✔
130

131
  for (const auto& lookupFormat : formats) {
7✔
132
    last = 0;
7✔
133
    while ((cur = lookupFormat.find("%", last)) != string::npos) {
14✔
134
      if (lookupFormat.compare(cur, 3, "%mp") == 0) {
7!
135
        return false;
×
136
      }
×
137

138
      if (lookupFormat.compare(cur, 2, "%%") == 0) { // Ensure escaped % is also accepted
7!
139
        last = cur + 2;
×
140
        continue;
×
141
      }
×
142
      last = cur + 1; // move to next attribute
7✔
143
    }
7✔
144
  }
7✔
145
  return true;
7✔
146
}
7✔
147

148
static vector<GeoIPDNSResourceRecord> makeDNSResourceRecord(GeoIPDomain& dom, DNSName name)
149
{
35✔
150
  GeoIPDNSResourceRecord resourceRecord;
35✔
151
  resourceRecord.domain_id = dom.id;
35✔
152
  resourceRecord.ttl = dom.ttl;
35✔
153
  resourceRecord.qname = std::move(name);
35✔
154
  resourceRecord.qtype = QType(0); // empty non terminal
35✔
155
  resourceRecord.content = "";
35✔
156
  resourceRecord.auth = true;
35✔
157
  resourceRecord.weight = 100;
35✔
158
  resourceRecord.has_weight = false;
35✔
159
  vector<GeoIPDNSResourceRecord> rrs;
35✔
160
  rrs.push_back(resourceRecord);
35✔
161
  return rrs;
35✔
162
}
35✔
163

164
void GeoIPBackend::setupNetmasks(const YAML::Node& domain, GeoIPDomain& dom)
165
{
14✔
166
  for (auto service = domain["services"].begin(); service != domain["services"].end(); service++) {
56✔
167
    unsigned int netmask4 = 0;
42✔
168
    unsigned int netmask6 = 0;
42✔
169
    DNSName serviceName{service->first.as<string>()};
42✔
170
    NetmaskTree<vector<string>> netmaskTree;
42✔
171

172
    // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
173
    if (service->second.IsMap()) {
42!
174
      for (auto net = service->second.begin(); net != service->second.end(); net++) {
×
175
        vector<string> value;
×
176
        if (net->second.IsSequence()) {
×
177
          value = net->second.as<vector<string>>();
×
178
        }
×
179
        else {
×
180
          value.push_back(net->second.as<string>());
×
181
        }
×
182
        if (net->first.as<string>() == "default") {
×
183
          netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
×
184
          netmaskTree.insert(Netmask("::/0")).second.swap(value);
×
185
        }
×
186
        else {
×
187
          Netmask netmask{net->first.as<string>()};
×
188
          netmaskTree.insert(netmask).second.swap(value);
×
189
          if (netmask.isIPv6() && netmask6 < netmask.getBits()) {
×
190
            netmask6 = netmask.getBits();
×
191
          }
×
192
          if (!netmask.isIPv6() && netmask4 < netmask.getBits()) {
×
193
            netmask4 = netmask.getBits();
×
194
          }
×
195
        }
×
196
      }
×
197
    }
×
198
    else {
42✔
199
      vector<string> value;
42✔
200
      if (service->second.IsSequence()) {
42!
201
        value = service->second.as<vector<string>>();
×
202
      }
×
203
      else {
42✔
204
        value.push_back(service->second.as<string>());
42✔
205
      }
42✔
206
      netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
42✔
207
      netmaskTree.insert(Netmask("::/0")).second.swap(value);
42✔
208
    }
42✔
209

210
    // Allow per domain override of mapping_lookup_formats and custom_mapping.
211
    // If not defined, the global values will be used.
212
    if (YAML::Node formats = domain["mapping_lookup_formats"]) {
42!
213
      auto mapping_lookup_formats = formats.as<vector<string>>();
×
214
      if (!validateMappingLookupFormats(mapping_lookup_formats)) {
×
215
        throw PDNSException(string("%mp is not allowed in mapping lookup formats of domain ") + dom.domain.toLogString());
×
216
      }
×
217

218
      dom.mapping_lookup_formats = std::move(mapping_lookup_formats);
×
219
    }
×
220
    else {
42✔
221
      dom.mapping_lookup_formats = d_global_mapping_lookup_formats;
42✔
222
    }
42✔
223
    if (YAML::Node mapping = domain["custom_mapping"]) {
42✔
224
      dom.custom_mapping = mapping.as<map<std::string, std::string>>();
7✔
225
    }
7✔
226
    else {
35✔
227
      dom.custom_mapping = d_global_custom_mapping;
35✔
228
    }
35✔
229

230
    dom.services[serviceName].netmask4 = netmask4;
42✔
231
    dom.services[serviceName].netmask6 = netmask6;
42✔
232
    dom.services[serviceName].masks.swap(netmaskTree);
42✔
233
  }
42✔
234
}
14✔
235

236
bool GeoIPBackend::loadDomain(const std::string& origin, const YAML::Node& domain, domainid_t domainID, GeoIPDomain& dom)
237
{
14✔
238
  try {
14✔
239
    dom.id = domainID;
14✔
240
    if (auto node = domain["domain"]) {
14!
241
      dom.domain = ZoneName(node.as<string>());
14✔
242
    }
14✔
243
    else {
×
244
      throw PDNSException("missing 'domain' node");
×
245
    }
×
246
    if (auto node = domain["ttl"]) {
14!
247
      dom.ttl = node.as<int>();
14✔
248
    }
14✔
249
    else {
×
250
      throw PDNSException("missing 'ttl' node");
×
251
    }
×
252

253
    for (auto recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
105✔
254
      ZoneName qname = ZoneName(recs->first.as<string>());
91✔
255
      vector<GeoIPDNSResourceRecord> rrs;
91✔
256

257
      for (auto item = recs->second.begin(); item != recs->second.end(); item++) {
245✔
258
        auto rec = item->begin();
154✔
259
        GeoIPDNSResourceRecord rr;
154✔
260
        rr.domain_id = dom.id;
154✔
261
        rr.ttl = dom.ttl;
154✔
262
        rr.qname = qname.operator const DNSName&();
154✔
263
        if (rec->first.IsNull()) {
154!
264
          rr.qtype = QType(0);
×
265
        }
×
266
        else {
154✔
267
          string qtype = boost::to_upper_copy(rec->first.as<string>());
154✔
268
          rr.qtype = qtype;
154✔
269
        }
154✔
270
        rr.has_weight = false;
154✔
271
        rr.weight = 100;
154✔
272
        if (rec->second.IsNull()) {
154!
273
          rr.content = "";
×
274
        }
×
275
        else if (rec->second.IsMap()) {
154✔
276
          for (YAML::const_iterator iter = rec->second.begin(); iter != rec->second.end(); iter++) {
42✔
277
            auto attr = iter->first.as<string>();
28✔
278
            if (attr == "content") {
28✔
279
              auto content = iter->second.as<string>();
14✔
280
              rr.content = std::move(content);
14✔
281
            }
14✔
282
            else if (attr == "weight") {
14!
283
              rr.weight = iter->second.as<int>();
14✔
284
              if (rr.weight <= 0) {
14!
285
                g_log << Logger::Error << "Weight must be positive for " << rr.qname << endl;
×
286
                throw PDNSException(string("Weight must be positive for ") + rr.qname.toLogString());
×
287
              }
×
288
              rr.has_weight = true;
14✔
289
            }
14✔
290
            else if (attr == "ttl") {
×
291
              rr.ttl = iter->second.as<int>();
×
292
            }
×
293
            else {
×
294
              g_log << Logger::Error << "Unsupported record attribute " << attr << " for " << rr.qname << endl;
×
295
              throw PDNSException(string("Unsupported record attribute ") + attr + string(" for ") + rr.qname.toLogString());
×
296
            }
×
297
          }
28✔
298
        }
14✔
299
        else {
140✔
300
          auto content = rec->second.as<string>();
140✔
301
          rr.content = std::move(content);
140✔
302
          rr.weight = 100;
140✔
303
        }
140✔
304
        rr.auth = true;
154✔
305
        rrs.push_back(rr);
154✔
306
      }
154✔
307
      std::swap(dom.records[qname.operator const DNSName&()], rrs);
91✔
308
    }
91✔
309

310
    setupNetmasks(domain, dom);
14✔
311

312
    // rectify the zone, first static records
313
    for (auto& item : dom.records) {
91✔
314
      // ensure we have parent in records
315
      DNSName name = item.first;
91✔
316
      while (name.chopOff() && name.isPartOf(dom.domain)) {
259!
317
        if (dom.records.find(name) == dom.records.end() && (dom.services.count(name) == 0U)) { // don't ENT out a service!
168✔
318
          auto rrs = makeDNSResourceRecord(dom, name);
35✔
319
          std::swap(dom.records[name], rrs);
35✔
320
        }
35✔
321
      }
168✔
322
    }
91✔
323

324
    // then services
325
    for (auto& item : dom.services) {
42✔
326
      // ensure we have parent in records
327
      DNSName name = item.first;
42✔
328
      while (name.chopOff() && name.isPartOf(dom.domain)) {
77!
329
        if (dom.records.find(name) == dom.records.end()) {
35!
330
          auto rrs = makeDNSResourceRecord(dom, name);
×
331
          std::swap(dom.records[name], rrs);
×
332
        }
×
333
      }
35✔
334
    }
42✔
335

336
    // finally fix weights
337
    for (auto& item : dom.records) {
126✔
338
      map<uint16_t, float> weights;
126✔
339
      map<uint16_t, float> sums;
126✔
340
      map<uint16_t, GeoIPDNSResourceRecord*> lasts;
126✔
341
      bool has_weight = false;
126✔
342
      // first we look for used weight
343
      for (const auto& resourceRecord : item.second) {
189✔
344
        weights[resourceRecord.qtype.getCode()] += static_cast<float>(resourceRecord.weight);
189✔
345
        if (resourceRecord.has_weight) {
189✔
346
          has_weight = true;
14✔
347
        }
14✔
348
      }
189✔
349
      if (has_weight) {
126✔
350
        // put them back as probabilities and values..
351
        for (auto& resourceRecord : item.second) {
14✔
352
          uint16_t rr_type = resourceRecord.qtype.getCode();
14✔
353
          resourceRecord.weight = static_cast<int>((static_cast<float>(resourceRecord.weight) / weights[rr_type]) * 1000.0);
14✔
354
          sums[rr_type] += static_cast<float>(resourceRecord.weight);
14✔
355
          resourceRecord.has_weight = has_weight;
14✔
356
          lasts[rr_type] = &resourceRecord;
14✔
357
        }
14✔
358
        // remove rounding gap
359
        for (auto& x : lasts) {
14✔
360
          float sum = sums[x.first];
14✔
361
          if (sum < 1000) {
14!
362
            x.second->weight += (1000 - sum);
×
363
          }
×
364
        }
14✔
365
      }
7✔
366
    }
126✔
367
  }
14✔
368
  catch (std::exception& ex) {
14✔
369
    g_log << Logger::Error << "Could not load zone from " << origin << ": " << ex.what() << endl;
×
370
    return false;
×
371
  }
×
372
  catch (PDNSException& ex) {
14✔
373
    g_log << Logger::Error << "Could not load zone from " << origin << ": " << ex.reason << endl;
×
374
    return false;
×
375
  }
×
376
  return true;
14✔
377
}
14✔
378

379
void GeoIPBackend::loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains)
380
{
×
381
  vector<std::filesystem::path> paths;
×
382
  for (const std::filesystem::path& p : std::filesystem::directory_iterator(std::filesystem::path(dir))) {
×
383
    if (std::filesystem::is_regular_file(p) && p.has_extension() && (p.extension() == ".yaml" || p.extension() == ".yml")) {
×
384
      paths.push_back(p);
×
385
    }
×
386
  }
×
387
  std::sort(paths.begin(), paths.end());
×
388
  for (const auto& p : paths) {
×
389
    std::string path = p.string();
×
390
    try {
×
391
      GeoIPDomain dom;
×
392
      const auto& zoneRoot = YAML::LoadFile(path);
×
393
      // expect zone key
394
      const auto& zone = zoneRoot["zone"];
×
395
      if (loadDomain(path, zone, static_cast<domainid_t>(domains.size()), dom)) {
×
396
        domains.push_back(dom);
×
397
      }
×
398
    }
×
399
    catch (std::exception& ex) {
×
400
      g_log << Logger::Warning << "Cannot load zone from " << path << ": " << ex.what() << endl;
×
401
    }
×
402
  }
×
403
}
×
404

405
void GeoIPBackend::initialize()
406
{
7✔
407
  YAML::Node config;
7✔
408
  vector<GeoIPDomain> tmp_domains;
7✔
409

410
  s_geoip_files.clear(); // reset pointers
7✔
411

412
  if (getArg("database-files").empty() == false) {
7✔
413
    vector<string> files;
6✔
414
    stringtok(files, getArg("database-files"), " ,\t\r\n");
6✔
415
    for (auto const& file : files) {
6✔
416
      s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
6✔
417
    }
6✔
418
  }
6✔
419

420
  if (s_geoip_files.empty()) {
7✔
421
    g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;
1✔
422
  }
1✔
423

424
  std::string zonesFile{getArg("zones-file")};
7✔
425
  if (!zonesFile.empty()) {
7!
426
    try {
7✔
427
      config = YAML::LoadFile(zonesFile);
7✔
428
    }
7✔
429
    catch (YAML::Exception& ex) {
7✔
430
      std::string description{};
×
431
      if (!ex.mark.is_null()) {
×
432
        description = "Configuration error in " + zonesFile + ", line " + std::to_string(ex.mark.line + 1) + ", column " + std::to_string(ex.mark.column + 1);
×
433
      }
×
434
      else {
×
435
        description = "Cannot read config file " + zonesFile;
×
436
      }
×
437
      throw PDNSException(description + ": " + ex.msg);
×
438
    }
×
439
  }
7✔
440

441
  // Global lookup formats and mapping will be used
442
  // if none defined at the domain level.
443
  if (YAML::Node formats = config["mapping_lookup_formats"]) {
7!
444
    d_global_mapping_lookup_formats = formats.as<vector<string>>();
7✔
445
    if (!validateMappingLookupFormats(d_global_mapping_lookup_formats)) {
7!
446
      throw PDNSException(string("%mp is not allowed in mapping lookup"));
×
447
    }
×
448
  }
7✔
449
  if (YAML::Node mapping = config["custom_mapping"]) {
7!
450
    d_global_custom_mapping = mapping.as<map<std::string, std::string>>();
7✔
451
  }
7✔
452

453
  std::string origin;
7✔
454
  for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
21✔
455
    GeoIPDomain dom;
14✔
456
    if (origin.empty()) {
14✔
457
      origin = "'domains' section in " + zonesFile;
7✔
458
    }
7✔
459
    if (loadDomain(origin, *_domain, static_cast<domainid_t>(tmp_domains.size()), dom)) {
14!
460
      tmp_domains.push_back(std::move(dom));
14✔
461
    }
14✔
462
  }
14✔
463

464
  if (YAML::Node domain_dir = config["zones_dir"]) {
7!
465
    loadDomainsFromDirectory(domain_dir.as<string>(), tmp_domains);
×
466
  }
×
467

468
  s_domains.clear();
7✔
469
  std::swap(s_domains, tmp_domains);
7✔
470

471
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
7✔
472
  g_getGeo = getGeoForLua;
7✔
473
}
7✔
474

475
GeoIPBackend::~GeoIPBackend()
476
{
6✔
477
  try {
6✔
478
    WriteLock writeLock(&s_state_lock);
6✔
479
    s_rc--;
6✔
480
    if (s_rc == 0) { // last instance gets to cleanup
6✔
481
      s_geoip_files.clear();
4✔
482
      s_domains.clear();
4✔
483
    }
4✔
484
  }
6✔
485
  catch (...) {
6✔
486
  }
×
487
}
6✔
488

489
bool GeoIPBackend::lookup_static(const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl)
490
{
1,288✔
491
  const auto& i = dom.records.find(search);
1,288✔
492
  map<uint16_t, int> cumul_probabilities;
1,288✔
493
  map<uint16_t, bool> weighted_match;
1,288✔
494
  int probability_rnd = 1 + (dns_random(1000)); // setting probability=0 means it never is used
1,288✔
495

496
  if (i != dom.records.end()) { // return static value
1,288✔
497
    for (const auto& rr : i->second) {
2,011✔
498
      if ((qtype != QType::ANY && rr.qtype != qtype) || weighted_match[rr.qtype.getCode()])
2,011!
499
        continue;
29✔
500

501
      if (rr.has_weight) {
1,982✔
502
        gl.netmask = (addr.isIPv6() ? 128 : 32);
132!
503
        int comp = cumul_probabilities[rr.qtype.getCode()];
132✔
504
        cumul_probabilities[rr.qtype.getCode()] += rr.weight;
132✔
505
        if (rr.weight == 0 || probability_rnd < comp || probability_rnd > (comp + rr.weight))
132!
506
          continue;
×
507
      }
132✔
508
      const string& content = format2str(rr.content, addr, gl, dom);
1,982✔
509
      if (rr.qtype != QType::ENT && rr.qtype != QType::TXT && content.empty())
1,982✔
510
        continue;
26✔
511
      d_result.push_back(rr);
1,956✔
512
      d_result.back().content = content;
1,956✔
513
      d_result.back().qname = qdomain;
1,956✔
514
      // If we are weighted we only return one resource and we found a matching resource,
515
      // so no need to check the other ones.
516
      if (rr.has_weight)
1,956✔
517
        weighted_match[rr.qtype.getCode()] = true;
132✔
518
    }
1,956✔
519
    // ensure we get most strict netmask
520
    for (DNSResourceRecord& rr : d_result) {
3,205✔
521
      rr.scopeMask = gl.netmask;
3,205✔
522
    }
3,205✔
523
    return true; // no need to go further
917✔
524
  }
917✔
525

526
  return false;
371✔
527
};
1,288✔
528

529
void GeoIPBackend::lookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, DNSPacket* pkt_p)
530
{
731✔
531
  ReadLock rl(&s_state_lock);
731✔
532
  const GeoIPDomain* dom;
731✔
533
  GeoIPNetmask gl;
731✔
534
  bool found = false;
731✔
535

536
  if (d_result.size() > 0)
731!
537
    throw PDNSException("Cannot perform lookup while another is running");
×
538

539
  d_result.clear();
731✔
540

541
  if (zoneId >= 0 && zoneId < static_cast<domainid_t>(s_domains.size())) {
731!
542
    dom = &(s_domains[zoneId]);
730✔
543
  }
730✔
544
  else {
1✔
545
    for (const GeoIPDomain& i : s_domains) { // this is arguably wrong, we should probably find the most specific match
1!
546
      if (qdomain.isPartOf(i.domain)) {
1!
547
        dom = &i;
1✔
548
        found = true;
1✔
549
        break;
1✔
550
      }
1✔
551
    }
1✔
552
    if (!found)
1!
553
      return; // not found
×
554
  }
1✔
555

556
  Netmask addr{"0.0.0.0/0"};
731✔
557
  if (pkt_p != nullptr) {
731✔
558
    addr = Netmask(pkt_p->getRealRemote());
481✔
559
  }
481✔
560

561
  gl.netmask = 0;
731✔
562

563
  (void)this->lookup_static(*dom, qdomain, qtype, qdomain, addr, gl);
731✔
564

565
  const auto& target = (*dom).services.find(qdomain);
731✔
566
  if (target == (*dom).services.end())
731✔
567
    return; // no hit
174✔
568

569
  const NetmaskTree<vector<string>>::node_type* node = target->second.masks.lookup(addr);
557✔
570
  if (node == nullptr)
557!
571
    return; // no hit, again.
×
572

573
  DNSName sformat;
557✔
574
  gl.netmask = node->first.getBits();
557✔
575
  // figure out smallest sensible netmask
576
  if (gl.netmask == 0) {
557!
577
    GeoIPNetmask tmp_gl;
557✔
578
    tmp_gl.netmask = 0;
557✔
579
    // get netmask from geoip backend
580
    if (queryGeoIP(addr, GeoIPInterface::Name, tmp_gl) == "unknown") {
557✔
581
      if (addr.isIPv6())
549!
582
        gl.netmask = target->second.netmask6;
×
583
      else
549✔
584
        gl.netmask = target->second.netmask4;
549✔
585
    }
549✔
586
  }
557✔
587
  else {
×
588
    if (addr.isIPv6())
×
589
      gl.netmask = target->second.netmask6;
×
590
    else
×
591
      gl.netmask = target->second.netmask4;
×
592
  }
×
593

594
  // note that this means the array format won't work with indirect
595
  for (auto it = node->second.begin(); it != node->second.end(); it++) {
675✔
596
    sformat = DNSName(format2str(*it, addr, gl, *dom));
557✔
597

598
    // see if the record can be found
599
    if (this->lookup_static((*dom), sformat, qtype, qdomain, addr, gl))
557✔
600
      return;
439✔
601
  }
557✔
602

603
  if (!d_result.empty()) {
118!
604
    g_log << Logger::Error << "Cannot have static record and CNAME at the same time."
×
605
          << "Please fix your configuration for \"" << qdomain << "\", so that "
×
606
          << "it can be resolved by GeoIP backend directly." << std::endl;
×
607
    d_result.clear();
×
608
    return;
×
609
  }
×
610

611
  // we need this line since we otherwise claim to have NS records etc
612
  if (!(qtype == QType::ANY || qtype == QType::CNAME))
118!
613
    return;
×
614

615
  DNSResourceRecord rr;
118✔
616
  rr.domain_id = dom->id;
118✔
617
  rr.qtype = QType::CNAME;
118✔
618
  rr.qname = qdomain;
118✔
619
  rr.content = sformat.toString();
118✔
620
  rr.auth = 1;
118✔
621
  rr.ttl = dom->ttl;
118✔
622
  rr.scopeMask = gl.netmask;
118✔
623
  d_result.push_back(rr);
118✔
624
}
118✔
625

626
bool GeoIPBackend::get(DNSResourceRecord& r)
627
{
2,782✔
628
  if (d_result.empty()) {
2,782✔
629
    return false;
708✔
630
  }
708✔
631

632
  r = d_result.back();
2,074✔
633
  d_result.pop_back();
2,074✔
634

635
  return true;
2,074✔
636
}
2,782✔
637

638
void GeoIPBackend::lookupEnd()
639
{
24✔
640
  d_result.clear();
24✔
641
}
24✔
642

643
static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl)
644
{
1,277✔
645
  string ret = "unknown";
1,277✔
646

647
  for (auto const& gi : s_geoip_files) {
1,277✔
648
    string val;
1,273✔
649
    const string ip = addr.toStringNoMask();
1,273✔
650
    bool found = false;
1,273✔
651

652
    switch (attribute) {
1,273!
653
    case GeoIPInterface::ASn:
×
654
      if (addr.isIPv6())
×
655
        found = gi->queryASnumV6(val, gl, ip);
×
656
      else
×
657
        found = gi->queryASnum(val, gl, ip);
×
658
      break;
×
659
    case GeoIPInterface::Name:
555✔
660
      if (addr.isIPv6())
555!
661
        found = gi->queryNameV6(val, gl, ip);
×
662
      else
555✔
663
        found = gi->queryName(val, gl, ip);
555✔
664
      break;
555✔
665
    case GeoIPInterface::Continent:
511✔
666
      if (addr.isIPv6())
511!
667
        found = gi->queryContinentV6(val, gl, ip);
×
668
      else
511✔
669
        found = gi->queryContinent(val, gl, ip);
511✔
670
      break;
511✔
671
    case GeoIPInterface::Region:
69✔
672
      if (addr.isIPv6())
69!
673
        found = gi->queryRegionV6(val, gl, ip);
×
674
      else
69✔
675
        found = gi->queryRegion(val, gl, ip);
69✔
676
      break;
69✔
677
    case GeoIPInterface::Country:
×
678
      if (addr.isIPv6())
×
679
        found = gi->queryCountryV6(val, gl, ip);
×
680
      else
×
681
        found = gi->queryCountry(val, gl, ip);
×
682
      break;
×
683
    case GeoIPInterface::Country2:
69✔
684
      if (addr.isIPv6())
69!
685
        found = gi->queryCountry2V6(val, gl, ip);
×
686
      else
69✔
687
        found = gi->queryCountry2(val, gl, ip);
69✔
688
      break;
69✔
689
    case GeoIPInterface::City:
69✔
690
      if (addr.isIPv6())
69!
691
        found = gi->queryCityV6(val, gl, ip);
×
692
      else
69✔
693
        found = gi->queryCity(val, gl, ip);
69✔
694
      break;
69✔
695
    case GeoIPInterface::Location:
×
696
      double lat = 0, lon = 0;
×
697
      std::optional<int> alt;
×
698
      std::optional<int> prec;
×
699
      if (addr.isIPv6())
×
700
        found = gi->queryLocationV6(gl, ip, lat, lon, alt, prec);
×
701
      else
×
702
        found = gi->queryLocation(gl, ip, lat, lon, alt, prec);
×
703
      val = std::to_string(lat) + " " + std::to_string(lon);
×
704
      break;
×
705
    }
1,273✔
706

707
    if (!found || val.empty() || val == "--") {
1,273!
708
      continue; // try next database
1,028✔
709
    }
1,028✔
710
    ret = std::move(val);
245✔
711
    std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
245✔
712
    break;
245✔
713
  }
1,273✔
714

715
  if (ret == "unknown")
1,277✔
716
    gl.netmask = (addr.isIPv6() ? 128 : 32); // prevent caching
1,032!
717
  return ret;
1,277✔
718
}
1,277✔
719

720
string getGeoForLua(const std::string& ip, int qaint)
721
{
×
722
  GeoIPInterface::GeoIPQueryAttribute qa((GeoIPInterface::GeoIPQueryAttribute)qaint);
×
723
  try {
×
724
    const Netmask addr{ip};
×
725
    GeoIPNetmask gl;
×
726
    string res = queryGeoIP(addr, qa, gl);
×
727
    //    cout<<"Result for "<<ip<<" lookup: "<<res<<endl;
728
    if (qa == GeoIPInterface::ASn && boost::starts_with(res, "as"))
×
729
      return res.substr(2);
×
730
    return res;
×
731
  }
×
732
  catch (std::exception& e) {
×
733
    cout << "Error: " << e.what() << endl;
×
734
  }
×
735
  catch (PDNSException& e) {
×
736
    cout << "Error: " << e.reason << endl;
×
737
  }
×
738
  return "";
×
739
}
×
740

741
static bool queryGeoLocation(const Netmask& addr, GeoIPNetmask& gl, double& lat, double& lon,
742
                             std::optional<int>& alt, std::optional<int>& prec)
743
{
150✔
744
  for (auto const& gi : s_geoip_files) {
150✔
745
    string val;
150✔
746
    if (addr.isIPv6()) {
150!
747
      if (gi->queryLocationV6(gl, addr.toStringNoMask(), lat, lon, alt, prec))
×
748
        return true;
×
749
    }
×
750
    else if (gi->queryLocation(gl, addr.toStringNoMask(), lat, lon, alt, prec))
150✔
751
      return true;
108✔
752
  }
150✔
753
  return false;
42✔
754
}
150✔
755

756
string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom)
757
{
2,651✔
758
  string::size_type cur, last;
2,651✔
759
  std::optional<int> alt;
2,651✔
760
  std::optional<int> prec;
2,651✔
761
  double lat, lon;
2,651✔
762
  time_t t = time(nullptr);
2,651✔
763
  GeoIPNetmask tmp_gl; // largest wins
2,651✔
764
  struct tm gtm;
2,651✔
765
  gmtime_r(&t, &gtm);
2,651✔
766
  last = 0;
2,651✔
767

768
  while ((cur = sformat.find('%', last)) != string::npos) {
3,723✔
769
    string rep;
1,072✔
770
    int nrep = 3;
1,072✔
771
    tmp_gl.netmask = 0;
1,072✔
772
    if (!sformat.compare(cur, 3, "%mp")) {
1,072✔
773
      rep = "unknown";
112✔
774
      for (const auto& lookupFormat : dom.mapping_lookup_formats) {
112✔
775
        auto it = dom.custom_mapping.find(format2str(lookupFormat, addr, gl, dom));
112✔
776
        if (it != dom.custom_mapping.end()) {
112✔
777
          rep = it->second;
12✔
778
          break;
12✔
779
        }
12✔
780
      }
112✔
781
    }
112✔
782
    else if (!sformat.compare(cur, 3, "%cn")) {
960✔
783
      rep = queryGeoIP(addr, GeoIPInterface::Continent, tmp_gl);
513✔
784
    }
513✔
785
    else if (!sformat.compare(cur, 3, "%co")) {
447!
786
      rep = queryGeoIP(addr, GeoIPInterface::Country, tmp_gl);
×
787
    }
×
788
    else if (!sformat.compare(cur, 3, "%cc")) {
447✔
789
      rep = queryGeoIP(addr, GeoIPInterface::Country2, tmp_gl);
69✔
790
    }
69✔
791
    else if (!sformat.compare(cur, 3, "%af")) {
378!
792
      rep = (addr.isIPv6() ? "v6" : "v4");
×
793
    }
×
794
    else if (!sformat.compare(cur, 3, "%as")) {
378!
795
      rep = queryGeoIP(addr, GeoIPInterface::ASn, tmp_gl);
×
796
    }
×
797
    else if (!sformat.compare(cur, 3, "%re")) {
378✔
798
      rep = queryGeoIP(addr, GeoIPInterface::Region, tmp_gl);
69✔
799
    }
69✔
800
    else if (!sformat.compare(cur, 3, "%na")) {
309!
801
      rep = queryGeoIP(addr, GeoIPInterface::Name, tmp_gl);
×
802
    }
×
803
    else if (!sformat.compare(cur, 3, "%ci")) {
309✔
804
      rep = queryGeoIP(addr, GeoIPInterface::City, tmp_gl);
69✔
805
    }
69✔
806
    else if (!sformat.compare(cur, 4, "%loc")) {
240✔
807
      char ns, ew;
50✔
808
      int d1, d2, m1, m2;
50✔
809
      double s1, s2;
50✔
810
      if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
50✔
811
        rep = "";
14✔
812
        tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
14!
813
      }
14✔
814
      else {
36✔
815
        ns = (lat > 0) ? 'N' : 'S';
36✔
816
        ew = (lon > 0) ? 'E' : 'W';
36✔
817
        /* remove sign */
818
        lat = fabs(lat);
36✔
819
        lon = fabs(lon);
36✔
820
        d1 = static_cast<int>(lat);
36✔
821
        d2 = static_cast<int>(lon);
36✔
822
        m1 = static_cast<int>((lat - d1) * 60.0);
36✔
823
        m2 = static_cast<int>((lon - d2) * 60.0);
36✔
824
        s1 = static_cast<double>(lat - d1 - m1 / 60.0) * 3600.0;
36✔
825
        s2 = static_cast<double>(lon - d2 - m2 / 60.0) * 3600.0;
36✔
826
        rep = str(boost::format("%d %d %0.3f %c %d %d %0.3f %c") % d1 % m1 % s1 % ns % d2 % m2 % s2 % ew);
36✔
827
        if (alt)
36!
828
          rep = rep + str(boost::format(" %d.00") % *alt);
×
829
        else
36✔
830
          rep = rep + string(" 0.00");
36✔
831
        if (prec)
36✔
832
          rep = rep + str(boost::format(" %dm") % *prec);
4✔
833
      }
36✔
834
      nrep = 4;
50✔
835
    }
50✔
836
    else if (!sformat.compare(cur, 4, "%lat")) {
190✔
837
      if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
50✔
838
        rep = "";
14✔
839
        tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
14!
840
      }
14✔
841
      else {
36✔
842
        rep = str(boost::format("%lf") % lat);
36✔
843
      }
36✔
844
      nrep = 4;
50✔
845
    }
50✔
846
    else if (!sformat.compare(cur, 4, "%lon")) {
140✔
847
      if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) {
50✔
848
        rep = "";
14✔
849
        tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
14!
850
      }
14✔
851
      else {
36✔
852
        rep = str(boost::format("%lf") % lon);
36✔
853
      }
36✔
854
      nrep = 4;
50✔
855
    }
50✔
856
    else if (!sformat.compare(cur, 3, "%hh")) {
90!
857
      rep = boost::str(boost::format("%02d") % gtm.tm_hour);
×
858
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
859
    }
×
860
    else if (!sformat.compare(cur, 3, "%yy")) {
90!
861
      rep = boost::str(boost::format("%02d") % (gtm.tm_year + 1900));
×
862
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
863
    }
×
864
    else if (!sformat.compare(cur, 3, "%dd")) {
90!
865
      rep = boost::str(boost::format("%02d") % (gtm.tm_yday + 1));
×
866
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
867
    }
×
868
    else if (!sformat.compare(cur, 4, "%wds")) {
90!
869
      nrep = 4;
×
870
      rep = GeoIP_WEEKDAYS.at(gtm.tm_wday);
×
871
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
872
    }
×
873
    else if (!sformat.compare(cur, 4, "%mos")) {
90!
874
      nrep = 4;
×
875
      rep = GeoIP_MONTHS.at(gtm.tm_mon);
×
876
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
877
    }
×
878
    else if (!sformat.compare(cur, 3, "%wd")) {
90!
879
      rep = boost::str(boost::format("%02d") % (gtm.tm_wday + 1));
×
880
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
881
    }
×
882
    else if (!sformat.compare(cur, 3, "%mo")) {
90!
883
      rep = boost::str(boost::format("%02d") % (gtm.tm_mon + 1));
×
884
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
885
    }
×
886
    else if (!sformat.compare(cur, 4, "%ip6")) {
90✔
887
      nrep = 4;
12✔
888
      if (addr.isIPv6())
12✔
889
        rep = addr.toStringNoMask();
6✔
890
      else
6✔
891
        rep = "";
6✔
892
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
12✔
893
    }
12✔
894
    else if (!sformat.compare(cur, 4, "%ip4")) {
78!
895
      nrep = 4;
78✔
896
      if (!addr.isIPv6())
78✔
897
        rep = addr.toStringNoMask();
72✔
898
      else
6✔
899
        rep = "";
6✔
900
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
78✔
901
    }
78✔
902
    else if (!sformat.compare(cur, 3, "%ip")) {
×
903
      rep = addr.toStringNoMask();
×
904
      tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
×
905
    }
×
906
    else if (!sformat.compare(cur, 2, "%%")) {
×
907
      last = cur + 2;
×
908
      continue;
×
909
    }
×
910
    else {
×
911
      last = cur + 1;
×
912
      continue;
×
913
    }
×
914
    if (tmp_gl.netmask > gl.netmask)
1,072✔
915
      gl.netmask = tmp_gl.netmask;
596✔
916
    sformat.replace(cur, nrep, rep);
1,072✔
917
    last = cur + rep.size(); // move to next attribute
1,072✔
918
  }
1,072✔
919
  return sformat;
2,651✔
920
}
2,651✔
921

922
void GeoIPBackend::reload()
923
{
×
924
  WriteLock wl(&s_state_lock);
×
925

926
  try {
×
927
    initialize();
×
928
  }
×
929
  catch (PDNSException& pex) {
×
930
    g_log << Logger::Error << "GeoIP backend reload failed: " << pex.reason << endl;
×
931
  }
×
932
  catch (std::exception& stex) {
×
933
    g_log << Logger::Error << "GeoIP backend reload failed: " << stex.what() << endl;
×
934
  }
×
935
  catch (...) {
×
936
    g_log << Logger::Error << "GeoIP backend reload failed" << endl;
×
937
  }
×
938
}
×
939

940
void GeoIPBackend::rediscover(string* /* status */)
941
{
×
942
  reload();
×
943
}
×
944

945
bool GeoIPBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool /* getSerial */)
946
{
1✔
947
  ReadLock rl(&s_state_lock);
1✔
948

949
  for (const GeoIPDomain& dom : s_domains) {
1!
950
    if (dom.domain == domain) {
1!
951
      SOAData sd;
1✔
952
      this->getSOA(dom.domain, dom.id, sd);
1✔
953
      info.id = dom.id;
1✔
954
      info.zone = dom.domain;
1✔
955
      info.serial = sd.serial;
1✔
956
      info.kind = DomainInfo::Native;
1✔
957
      info.backend = this;
1✔
958
      return true;
1✔
959
    }
1✔
960
  }
1✔
UNCOV
961
  return false;
×
962
}
1✔
963

964
void GeoIPBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool /* include_disabled */)
965
{
3✔
966
  ReadLock rl(&s_state_lock);
3✔
967

968
  DomainInfo di;
3✔
969
  for (const auto& dom : s_domains) {
6✔
970
    SOAData sd;
6✔
971
    this->getSOA(dom.domain, dom.id, sd);
6✔
972
    di.id = dom.id;
6✔
973
    di.zone = dom.domain;
6✔
974
    di.serial = sd.serial;
6✔
975
    di.kind = DomainInfo::Native;
6✔
976
    di.backend = this;
6✔
977
    domains->emplace_back(di);
6✔
978
  }
6✔
979
}
3✔
980

981
bool GeoIPBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
982
{
7✔
983
  if (!d_dnssec)
7✔
984
    return false;
4✔
985

986
  ReadLock rl(&s_state_lock);
3✔
987
  for (const GeoIPDomain& dom : s_domains) {
4!
988
    if (dom.domain == name) {
4✔
989
      if (hasDNSSECkey(dom.domain)) {
3✔
990
        meta[string("NSEC3NARROW")].push_back("1");
1✔
991
        meta[string("NSEC3PARAM")].push_back("1 0 0 -");
1✔
992
      }
1✔
993
      return true;
3✔
994
    }
3✔
995
  }
4✔
996
  return false;
×
997
}
3✔
998

999
bool GeoIPBackend::getDomainMetadata(const ZoneName& name, const std::string& kind, std::vector<std::string>& meta)
1000
{
×
1001
  if (!d_dnssec)
×
1002
    return false;
×
1003

1004
  ReadLock rl(&s_state_lock);
×
1005
  for (const GeoIPDomain& dom : s_domains) {
×
1006
    if (dom.domain == name) {
×
1007
      if (hasDNSSECkey(dom.domain)) {
×
1008
        if (kind == "NSEC3NARROW")
×
1009
          meta.push_back(string("1"));
×
1010
        if (kind == "NSEC3PARAM")
×
1011
          meta.push_back(string("1 0 1 f95a"));
×
1012
      }
×
1013
      return true;
×
1014
    }
×
1015
  }
×
1016
  return false;
×
1017
}
×
1018

1019
bool GeoIPBackend::getDomainKeys(const ZoneName& name, std::vector<DNSBackend::KeyData>& keys)
1020
{
8✔
1021
  if (!d_dnssec)
8✔
1022
    return false;
4✔
1023
  ReadLock rl(&s_state_lock);
4✔
1024
  for (const GeoIPDomain& dom : s_domains) {
5!
1025
    if (dom.domain == name) {
5✔
1026
      regex_t reg;
4✔
1027
      regmatch_t regm[5];
4✔
1028
      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
4✔
1029
      ostringstream pathname;
4✔
1030
      pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
4✔
1031
      glob_t glob_result;
4✔
1032
      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
4✔
1033
        for (size_t i = 0; i < glob_result.gl_pathc; i++) {
4✔
1034
          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
2!
1035
            DNSBackend::KeyData kd;
2✔
1036
            pdns::checked_stoi_into(kd.id, glob_result.gl_pathv[i] + regm[3].rm_so);
2✔
1037
            kd.active = !strncmp(glob_result.gl_pathv[i] + regm[4].rm_so, "1", 1);
2✔
1038
            kd.published = true;
2✔
1039
            pdns::checked_stoi_into(kd.flags, glob_result.gl_pathv[i] + regm[2].rm_so);
2✔
1040
            ifstream ifs(glob_result.gl_pathv[i]);
2✔
1041
            ostringstream content;
2✔
1042
            char buffer[1024];
2✔
1043
            while (ifs.good()) {
4✔
1044
              ifs.read(buffer, sizeof buffer);
2✔
1045
              if (ifs.gcount() > 0) {
2!
1046
                content << string(buffer, ifs.gcount());
2✔
1047
              }
2✔
1048
            }
2✔
1049
            ifs.close();
2✔
1050
            kd.content = content.str();
2✔
1051
            keys.emplace_back(std::move(kd));
2✔
1052
          }
2✔
1053
        }
2✔
1054
      }
2✔
1055
      regfree(&reg);
4✔
1056
      globfree(&glob_result);
4✔
1057
      return true;
4✔
1058
    }
4✔
1059
  }
5✔
1060
  return false;
×
1061
}
4✔
1062

1063
bool GeoIPBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
1064
{
×
1065
  if (!d_dnssec)
×
1066
    return false;
×
1067
  WriteLock rl(&s_state_lock);
×
1068
  ostringstream path;
×
1069

1070
  for (const GeoIPDomain& dom : s_domains) {
×
1071
    if (dom.domain == name) {
×
1072
      regex_t reg;
×
1073
      regmatch_t regm[5];
×
1074
      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
×
1075
      ostringstream pathname;
×
1076
      pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
×
1077
      glob_t glob_result;
×
1078
      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
×
1079
        for (size_t i = 0; i < glob_result.gl_pathc; i++) {
×
1080
          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
×
1081
            auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
×
1082
            if (kid == keyId) {
×
1083
              if (unlink(glob_result.gl_pathv[i])) {
×
1084
                cerr << "Cannot delete key:" << strerror(errno) << endl;
×
1085
              }
×
1086
              break;
×
1087
            }
×
1088
          }
×
1089
        }
×
1090
      }
×
1091
      regfree(&reg);
×
1092
      globfree(&glob_result);
×
1093
      return true;
×
1094
    }
×
1095
  }
×
1096
  return false;
×
1097
}
×
1098

1099
bool GeoIPBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
1100
{
1✔
1101
  if (!d_dnssec)
1!
1102
    return false;
×
1103
  WriteLock rl(&s_state_lock);
1✔
1104
  unsigned int nextid = 1;
1✔
1105

1106
  for (const GeoIPDomain& dom : s_domains) {
1!
1107
    if (dom.domain == name) {
1!
1108
      regex_t reg;
1✔
1109
      regmatch_t regm[5];
1✔
1110
      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
1✔
1111
      ostringstream pathname;
1✔
1112
      pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
1✔
1113
      glob_t glob_result;
1✔
1114
      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
1!
1115
        for (size_t i = 0; i < glob_result.gl_pathc; i++) {
×
1116
          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
×
1117
            auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
×
1118
            if (kid >= nextid)
×
1119
              nextid = kid + 1;
×
1120
          }
×
1121
        }
×
1122
      }
×
1123
      regfree(&reg);
1✔
1124
      globfree(&glob_result);
1✔
1125
      pathname.str("");
1✔
1126
      pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "." << key.flags << "." << nextid << "." << (key.active ? "1" : "0") << ".key";
1!
1127
      ofstream ofs(pathname.str().c_str());
1✔
1128
      ofs.write(key.content.c_str(), key.content.size());
1✔
1129
      ofs.close();
1✔
1130
      keyId = nextid;
1✔
1131
      return true;
1✔
1132
    }
1✔
1133
  }
1✔
1134
  return false;
×
1135
}
1✔
1136

1137
bool GeoIPBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
1138
{
×
1139
  if (!d_dnssec)
×
1140
    return false;
×
1141
  WriteLock rl(&s_state_lock);
×
1142
  for (const GeoIPDomain& dom : s_domains) {
×
1143
    if (dom.domain == name) {
×
1144
      regex_t reg;
×
1145
      regmatch_t regm[5];
×
1146
      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
×
1147
      ostringstream pathname;
×
1148
      pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
×
1149
      glob_t glob_result;
×
1150
      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
×
1151
        for (size_t i = 0; i < glob_result.gl_pathc; i++) {
×
1152
          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
×
1153
            auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
×
1154
            if (kid == keyId && strcmp(glob_result.gl_pathv[i] + regm[4].rm_so, "0") == 0) { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
×
1155
              ostringstream newpath;
×
1156
              newpath << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "." << pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[2].rm_so) << "." << kid << ".1.key";
×
1157
              if (rename(glob_result.gl_pathv[i], newpath.str().c_str())) {
×
1158
                cerr << "Cannot activate key: " << strerror(errno) << endl;
×
1159
              }
×
1160
            }
×
1161
          }
×
1162
        }
×
1163
      }
×
1164
      globfree(&glob_result);
×
1165
      regfree(&reg);
×
1166
      return true;
×
1167
    }
×
1168
  }
×
1169
  return false;
×
1170
}
×
1171

1172
bool GeoIPBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
1173
{
×
1174
  if (!d_dnssec)
×
1175
    return false;
×
1176
  WriteLock rl(&s_state_lock);
×
1177
  for (const GeoIPDomain& dom : s_domains) {
×
1178
    if (dom.domain == name) {
×
1179
      regex_t reg;
×
1180
      regmatch_t regm[5];
×
1181
      regcomp(&reg, "(.*)[.]([0-9]+)[.]([0-9]+)[.]([01])[.]key$", REG_ICASE | REG_EXTENDED);
×
1182
      ostringstream pathname;
×
1183
      pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
×
1184
      glob_t glob_result;
×
1185
      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
×
1186
        for (size_t i = 0; i < glob_result.gl_pathc; i++) {
×
1187
          if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
×
1188
            auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
×
1189
            if (kid == keyId && strcmp(glob_result.gl_pathv[i] + regm[4].rm_so, "1") == 0) { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
×
1190
              ostringstream newpath;
×
1191
              newpath << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "." << pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[2].rm_so) << "." << kid << ".0.key";
×
1192
              if (rename(glob_result.gl_pathv[i], newpath.str().c_str())) {
×
1193
                cerr << "Cannot deactivate key: " << strerror(errno) << endl;
×
1194
              }
×
1195
            }
×
1196
          }
×
1197
        }
×
1198
      }
×
1199
      globfree(&glob_result);
×
1200
      regfree(&reg);
×
1201
      return true;
×
1202
    }
×
1203
  }
×
1204
  return false;
×
1205
}
×
1206

1207
bool GeoIPBackend::publishDomainKey(const ZoneName& /* name */, unsigned int /* id */)
1208
{
×
1209
  return false;
×
1210
}
×
1211

1212
bool GeoIPBackend::unpublishDomainKey(const ZoneName& /* name */, unsigned int /* id */)
1213
{
×
1214
  return false;
×
1215
}
×
1216

1217
bool GeoIPBackend::hasDNSSECkey(const ZoneName& name)
1218
{
3✔
1219
  ostringstream pathname;
3✔
1220
  pathname << getArg("dnssec-keydir") << "/" << name.toStringNoDot() << "*.key";
3✔
1221
  glob_t glob_result;
3✔
1222
  if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
3✔
1223
    globfree(&glob_result);
1✔
1224
    return true;
1✔
1225
  }
1✔
1226
  return false;
2✔
1227
}
3✔
1228

1229
class GeoIPFactory : public BackendFactory
1230
{
1231
public:
1232
  GeoIPFactory() :
1233
    BackendFactory("geoip") {}
5,555✔
1234

1235
  void declareArguments(const string& suffix = "") override
1236
  {
4✔
1237
    declare(suffix, "zones-file", "YAML file to load zone(s) configuration", "");
4✔
1238
    declare(suffix, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
4✔
1239
    declare(suffix, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
4✔
1240
  }
4✔
1241

1242
  DNSBackend* make(const string& suffix) override
1243
  {
12✔
1244
    return new GeoIPBackend(suffix);
12✔
1245
  }
12✔
1246
};
1247

1248
class GeoIPLoader
1249
{
1250
public:
1251
  GeoIPLoader()
1252
  {
5,555✔
1253
    BackendMakers().report(std::make_unique<GeoIPFactory>());
5,555✔
1254
    g_log << Logger::Info << "[geoipbackend] This is the geoip backend version " VERSION
5,555✔
1255
#ifndef REPRODUCIBLE
5,555✔
1256
          << " (" __DATE__ " " __TIME__ ")"
5,555✔
1257
#endif
5,555✔
1258
          << " reporting" << endl;
5,555✔
1259
  }
5,555✔
1260
};
1261

1262
static GeoIPLoader geoiploader;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc