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

PowerDNS / pdns / 14497954401

16 Apr 2025 04:40PM UTC coverage: 63.538% (+0.05%) from 63.485%
14497954401

Pull #15441

github

web-flow
Merge 8bfeeeb15 into f7c7e299c
Pull Request #15441: [evil] ZoneName, step 2

41812 of 100540 branches covered (41.59%)

Branch coverage included in aggregate %.

357 of 407 new or added lines in 38 files covered. (87.71%)

38 existing lines in 10 files now uncovered.

129019 of 168323 relevant lines covered (76.65%)

3923687.88 hits per line

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

39.3
/modules/ldapbackend/native.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 * originally authored by Norbert Sendetzky
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of version 2 of the GNU General Public License as
8
 * published by the Free Software Foundation.
9
 *
10
 * In addition, for the avoidance of any doubt, permission is granted to
11
 * link this program with OpenSSL and to (re)distribute the binaries
12
 * produced as the result of such linking.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
 */
23
#include "exceptions.hh"
24
#include "ldapbackend.hh"
25
#include <cstdlib>
26

27
bool LdapBackend::list(const ZoneName& target, int domain_id, bool /* include_disabled */)
28
{
×
29
  try {
×
30
    d_in_list = true;
×
NEW
31
    d_qname = target.operator const DNSName&();
×
32
    d_qtype = QType::ANY;
×
33
    d_results_cache.clear();
×
34

35
    return (this->*d_list_fcnt)(target, domain_id);
×
36
  }
×
37
  catch (LDAPTimeout& lt) {
×
38
    g_log << Logger::Warning << d_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
×
39
    throw DBException("LDAP server timeout");
×
40
  }
×
41
  catch (LDAPNoConnection& lnc) {
×
42
    g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
×
43
    if (reconnect())
×
44
      this->list(target, domain_id);
×
45
    else
×
46
      throw PDNSException("Failed to reconnect to LDAP server");
×
47
  }
×
48
  catch (LDAPException& le) {
×
49
    g_log << Logger::Error << d_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
×
50
    throw PDNSException("LDAP server unreachable"); // try to reconnect to another server
×
51
  }
×
52
  catch (std::exception& e) {
×
53
    g_log << Logger::Error << d_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
×
54
    throw DBException("STL exception");
×
55
  }
×
56

57
  return false;
×
58
}
×
59

60
bool LdapBackend::list_simple(const ZoneName& target, int /* domain_id */)
61
{
×
62
  string dn;
×
63
  string filter;
×
64
  string qesc;
×
65

66
  dn = getArg("basedn");
×
67
  qesc = toLower(d_pldap->escape(target.toStringRootDot()));
×
68

69
  // search for SOARecord of target
70
  filter = strbind(":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg("filter-axfr"));
×
71
  PowerLDAP::SearchResult::Ptr search = d_pldap->search(dn, LDAP_SCOPE_SUBTREE, filter, (const char**)ldap_attrany);
×
72
  if (!search->getNext(d_result, true))
×
73
    return false;
×
74

75
  if (d_result.count("dn") && !d_result["dn"].empty()) {
×
76
    if (!mustDo("basedn-axfr-override")) {
×
77
      dn = d_result["dn"][0];
×
78
    }
×
79
  }
×
80

81
  // If we have any records associated with this entry let's parse them here
82
  DNSResult soa_result;
×
83
  soa_result.ttl = d_default_ttl;
×
84
  soa_result.lastmod = 0;
×
85
  this->extract_common_attributes(soa_result);
×
86
  this->extract_entry_results(d_qname, soa_result, QType(uint16_t(QType::ANY)));
×
87

88
  filter = strbind(":target:", "associatedDomain=*." + qesc, getArg("filter-axfr"));
×
89
  g_log << Logger::Debug << d_myname << " Search = basedn: " << dn << ", filter: " << filter << endl;
×
90
  d_search = d_pldap->search(dn, LDAP_SCOPE_SUBTREE, filter, (const char**)ldap_attrany);
×
91

92
  return true;
×
93
}
×
94

95
bool LdapBackend::list_strict(const ZoneName& target, int domain_id)
96
{
×
97
  if (target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName("ip6.arpa"))) {
×
98
    g_log << Logger::Warning << d_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
×
99
    return false; // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
×
100
  }
×
101

102
  return list_simple(target, domain_id);
×
103
}
×
104

105
void LdapBackend::lookup(const QType& qtype, const DNSName& qname, int zoneid, DNSPacket* dnspkt)
106
{
1,235✔
107
  try {
1,235✔
108
    d_in_list = false;
1,235✔
109
    d_qname = qname;
1,235✔
110
    d_qtype = qtype;
1,235✔
111
    d_results_cache.clear();
1,235✔
112

113
    if (d_qlog) {
1,235!
114
      g_log.log("Query: '" + qname.toStringRootDot() + "|" + qtype.toString() + "'", Logger::Error);
1,235✔
115
    }
1,235✔
116
    (this->*d_lookup_fcnt)(qtype, qname, dnspkt, zoneid);
1,235✔
117
  }
1,235✔
118
  catch (LDAPTimeout& lt) {
1,235✔
119
    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
×
120
    throw DBException("LDAP server timeout");
×
121
  }
×
122
  catch (LDAPNoConnection& lnc) {
1,235✔
123
    g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
×
124
    if (reconnect())
×
125
      this->lookup(qtype, qname, zoneid, dnspkt);
×
126
    else
×
127
      throw PDNSException("Failed to reconnect to LDAP server");
×
128
  }
×
129
  catch (LDAPException& le) {
1,235✔
130
    g_log << Logger::Error << d_myname << " Unable to search LDAP directory: " << le.what() << endl;
×
131
    throw PDNSException("LDAP server unreachable"); // try to reconnect to another server
×
132
  }
×
133
  catch (std::exception& e) {
1,235✔
134
    g_log << Logger::Error << d_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
×
135
    throw DBException("STL exception");
×
136
  }
×
137
}
1,235✔
138

139
void LdapBackend::lookup_simple(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
140
{
413✔
141
  string filter, attr, qesc;
413✔
142
  const char** attributes = ldap_attrany + 1; // skip associatedDomain
413✔
143
  const char* attronly[] = {NULL, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL};
413✔
144

145
  qesc = toLower(d_pldap->escape(qname.toStringRootDot()));
413✔
146
  filter = "associatedDomain=" + qesc;
413✔
147

148
  if (qtype.getCode() != QType::ANY) {
413✔
149
    attr = qtype.toString() + "Record";
195✔
150
    filter = "&(" + filter + ")(" + attr + "=*)";
195✔
151
    attronly[0] = attr.c_str();
195✔
152
    attributes = attronly;
195✔
153
  }
195✔
154

155
  filter = strbind(":target:", filter, getArg("filter-lookup"));
413✔
156

157
  g_log << Logger::Debug << d_myname << " Search = basedn: " << getArg("basedn") << ", filter: " << filter << ", qtype: " << qtype.toString() << endl;
413✔
158
  d_search = d_pldap->search(getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, attributes);
413✔
159
}
413✔
160

161
void LdapBackend::lookup_strict(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
162
{
412✔
163
  int len;
412✔
164
  vector<string> parts;
412✔
165
  string filter, attr, qesc;
412✔
166
  const char** attributes = ldap_attrany + 1; // skip associatedDomain
412✔
167
  const char* attronly[] = {NULL, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL};
412✔
168

169
  qesc = toLower(d_pldap->escape(qname.toStringRootDot()));
412✔
170
  stringtok(parts, qesc, ".");
412✔
171
  len = qesc.length();
412✔
172

173
  if (parts.size() == 6 && len > 13 && qesc.substr(len - 13, 13) == ".in-addr.arpa") // IPv4 reverse lookups
412!
174
  {
2✔
175
    filter = "aRecord=" + ptr2ip4(parts);
2✔
176
    attronly[0] = "associatedDomain";
2✔
177
    attributes = attronly;
2✔
178
  }
2✔
179
  else if (parts.size() == 34 && len > 9 && (qesc.substr(len - 9, 9) == ".ip6.arpa")) // IPv6 reverse lookups
410!
180
  {
×
181
    filter = "aAAARecord=" + ptr2ip6(parts);
×
182
    attronly[0] = "associatedDomain";
×
183
    attributes = attronly;
×
184
  }
×
185
  else // IPv4 and IPv6 lookups
410✔
186
  {
410✔
187
    filter = "associatedDomain=" + qesc;
410✔
188
  }
410✔
189

190
  if (qtype.getCode() != QType::ANY) {
412✔
191
    attr = qtype.toString() + "Record";
195✔
192
    filter = "&(" + filter + ")(" + attr + "=*)";
195✔
193
    attronly[0] = attr.c_str();
195✔
194
    attributes = attronly;
195✔
195
  }
195✔
196

197
  filter = strbind(":target:", filter, getArg("filter-lookup"));
412✔
198

199
  g_log << Logger::Debug << d_myname << " Search = basedn: " << getArg("basedn") << ", filter: " << filter << ", qtype: " << qtype.toString() << endl;
412✔
200
  d_search = d_pldap->search(getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, attributes);
412✔
201
}
412✔
202

203
void LdapBackend::lookup_tree(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
204
{
410✔
205
  string filter, attr, qesc, dn;
410✔
206
  const char** attributes = ldap_attrany + 1; // skip associatedDomain
410✔
207
  const char* attronly[] = {NULL, "dNSTTL", "modifyTimestamp", "PdnsRecordTTL", "PdnsRecordAuth", "PdnsRecordOrdername", NULL};
410✔
208
  vector<string> parts;
410✔
209

210
  qesc = toLower(d_pldap->escape(qname.toStringRootDot()));
410✔
211
  filter = "associatedDomain=" + qesc;
410✔
212

213
  if (qtype.getCode() != QType::ANY) {
410✔
214
    attr = qtype.toString() + "Record";
194✔
215
    filter = "&(" + filter + ")(" + attr + "=*)";
194✔
216
    attronly[0] = attr.c_str();
194✔
217
    attributes = attronly;
194✔
218
  }
194✔
219

220
  filter = strbind(":target:", filter, getArg("filter-lookup"));
410✔
221

222
  stringtok(parts, toLower(qname.toString()), ".");
410✔
223
  for (auto i = parts.crbegin(); i != parts.crend(); i++) {
2,851✔
224
    dn = "dc=" + *i + "," + dn;
2,441✔
225
  }
2,441✔
226

227
  g_log << Logger::Debug << d_myname << " Search = basedn: " << dn + getArg("basedn") << ", filter: " << filter << ", qtype: " << qtype.toString() << endl;
410✔
228
  d_search = d_pldap->search(dn + getArg("basedn"), LDAP_SCOPE_BASE, filter, attributes);
410✔
229
}
410✔
230

231
bool LdapBackend::get(DNSResourceRecord& rr)
232
{
2,064✔
233
  if (d_results_cache.empty()) {
2,064✔
234
    while (d_results_cache.empty()) {
1,991✔
235
      bool exhausted = false;
1,613✔
236
      bool valid_entry_found = false;
1,613✔
237

238
      while (!valid_entry_found && !exhausted) {
3,226✔
239
        try {
1,613✔
240
          exhausted = !d_search->getNext(d_result, true);
1,613✔
241
        }
1,613✔
242
        catch (LDAPException& le) {
1,613✔
243
          g_log << Logger::Error << d_myname << " Failed to get next result: " << le.what() << endl;
×
244
          throw PDNSException("Get next result impossible");
×
245
        }
×
246

247
        if (!exhausted) {
1,613✔
248
          if (!d_in_list) {
378!
249
            // All entries are valid here
250
            valid_entry_found = true;
378✔
251
          }
378✔
252
          else {
×
253
            // If we're called after list() then the entry *must* contain
254
            // associatedDomain, otherwise let's just skip it
255
            if (d_result.count("associatedDomain"))
×
256
              valid_entry_found = true;
×
257
          }
×
258
        }
378✔
259
      }
1,613✔
260

261
      if (exhausted) {
1,613✔
262
        break;
1,235✔
263
      }
1,235✔
264

265
      DNSResult result_template;
378✔
266
      result_template.ttl = d_default_ttl;
378✔
267
      result_template.lastmod = 0;
378✔
268
      this->extract_common_attributes(result_template);
378✔
269

270
      std::vector<std::string> associatedDomains;
378✔
271

272
      if (d_result.count("associatedDomain")) {
378✔
273
        if (d_in_list) {
3!
274
          // We can have more than one associatedDomain in the entry, so for each of them we have to check
275
          // that they are indeed under the domain we've been asked to list (nothing enforces this, so you
276
          // can have one associatedDomain set to "host.first-domain.com" and another one set to
277
          // "host.second-domain.com"). Better not return the latter I guess :)
278
          // We also have to generate one DNSResult per DNS-relevant attribute. As we've asked only for them
279
          // and the others above we've already cleaned it's just a matter of iterating over them.
280

281
          unsigned int axfrqlen = d_qname.toStringRootDot().length();
×
282
          for (auto i = d_result["associatedDomain"].begin(); i != d_result["associatedDomain"].end(); ++i) {
×
283
            // Sanity checks: is this associatedDomain attribute under the requested domain?
284
            if (i->size() >= axfrqlen && i->substr(i->size() - axfrqlen, axfrqlen) == d_qname.toStringRootDot())
×
285
              associatedDomains.push_back(*i);
×
286
          }
×
287
        }
×
288
        else {
3✔
289
          // This was a lookup in strict mode, so we add the reverse lookup
290
          // information manually.
291
          d_result["pTRRecord"] = d_result["associatedDomain"];
3✔
292
        }
3✔
293
      }
3✔
294

295
      if (d_in_list) {
378!
296
        for (const auto& domain : associatedDomains)
×
297
          this->extract_entry_results(DNSName(domain), result_template, QType(uint16_t(QType::ANY)));
×
298
      }
×
299
      else {
378✔
300
        this->extract_entry_results(d_qname, result_template, QType(uint16_t(QType::ANY)));
378✔
301
      }
378✔
302
    }
378✔
303

304
    if (d_results_cache.empty())
1,613✔
305
      return false;
1,235✔
306
  }
1,613✔
307

308
  DNSResult result = d_results_cache.back();
829✔
309
  d_results_cache.pop_back();
829✔
310
  rr.qtype = result.qtype;
829✔
311
  rr.qname = result.qname;
829✔
312
  rr.ttl = result.ttl;
829✔
313
  rr.last_modified = 0;
829✔
314
  rr.content = result.value;
829✔
315
  rr.auth = result.auth;
829✔
316

317
  g_log << Logger::Debug << d_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).toString() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl;
829✔
318
  return true;
829✔
319
}
2,064✔
320

321
bool LdapBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool /* getSerial */)
322
{
×
323
  string filter;
×
324
  SOAData sd;
×
325
  PowerLDAP::sentry_t result;
×
326
  const char* attronly[] = {
×
327
    "sOARecord",
×
328
    "PdnsDomainId",
×
329
    "PdnsDomainNotifiedSerial",
×
330
    "PdnsDomainLastCheck",
×
331
    "PdnsDomainMaster",
×
332
    "PdnsDomainType",
×
333
    NULL};
×
334

335
  try {
×
336
    // search for SOARecord of domain
337
    filter = "(&(associatedDomain=" + toLower(d_pldap->escape(domain.toStringRootDot())) + ")(SOARecord=*))";
×
338
    d_search = d_pldap->search(getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, attronly);
×
339
    if (!d_search->getNext(result)) {
×
340
      return false;
×
341
    }
×
342
  }
×
343
  catch (LDAPTimeout& lt) {
×
344
    g_log << Logger::Warning << d_myname << " Unable to search LDAP directory: " << lt.what() << endl;
×
345
    throw DBException("LDAP server timeout");
×
346
  }
×
347
  catch (LDAPNoConnection& lnc) {
×
348
    g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
×
349
    if (reconnect())
×
350
      this->getDomainInfo(domain, info);
×
351
    else
×
352
      throw PDNSException("Failed to reconnect to LDAP server");
×
353
  }
×
354
  catch (LDAPException& le) {
×
355
    g_log << Logger::Error << d_myname << " Unable to search LDAP directory: " << le.what() << endl;
×
356
    throw PDNSException("LDAP server unreachable"); // try to reconnect to another server
×
357
  }
×
358
  catch (std::exception& e) {
×
359
    throw DBException("STL exception");
×
360
  }
×
361

362
  if (result.count("sOARecord") && !result["sOARecord"].empty()) {
×
363
    sd.serial = 0;
×
364
    fillSOAData(result["sOARecord"][0], sd);
×
365

366
    if (result.count("PdnsDomainId") && !result["PdnsDomainId"].empty())
×
367
      info.id = std::stoi(result["PdnsDomainId"][0]);
×
368
    else
×
369
      info.id = 0;
×
370

371
    info.serial = sd.serial;
×
372
    info.zone = domain;
×
373

374
    if (result.count("PdnsDomainLastCheck") && !result["PdnsDomainLastCheck"].empty())
×
375
      pdns::checked_stoi_into(info.last_check, result["PdnsDomainLastCheck"][0]);
×
376
    else
×
377
      info.last_check = 0;
×
378

379
    if (result.count("PdnsDomainNotifiedSerial") && !result["PdnsDomainNotifiedSerial"].empty())
×
380
      pdns::checked_stoi_into(info.notified_serial, result["PdnsDomainNotifiedSerial"][0]);
×
381
    else
×
382
      info.notified_serial = 0;
×
383

384
    if (result.count("PdnsDomainMaster") && !result["PdnsDomainMaster"].empty()) {
×
385
      for (const auto& m : result["PdnsDomainMaster"])
×
386
        info.primaries.emplace_back(m, 53);
×
387
    }
×
388

389
    if (result.count("PdnsDomainType") && !result["PdnsDomainType"].empty()) {
×
390
      string kind = result["PdnsDomainType"][0];
×
391
      if (kind == "master")
×
392
        info.kind = DomainInfo::Primary;
×
393
      else if (kind == "slave")
×
394
        info.kind = DomainInfo::Secondary;
×
395
      else
×
396
        info.kind = DomainInfo::Native;
×
397
    }
×
398
    else {
×
399
      info.kind = DomainInfo::Native;
×
400
    }
×
401

402
    info.backend = this;
×
403
    return true;
×
404
  }
×
405

406
  return false;
×
407
}
×
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

© 2026 Coveralls, Inc