Coverage Report

Created: 2026-07-24 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/dnsdistdist/dnsname.cc
Line
Count
Source
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 "dnsname.hh"
23
#include <boost/format.hpp>
24
#include <string>
25
#include <cinttypes>
26
27
#include "dnswriter.hh"
28
#include "misc.hh"
29
30
#include <boost/functional/hash.hpp>
31
32
const DNSName g_rootdnsname(".");
33
const DNSName g_wildcarddnsname("*");
34
const DNSName g_coodnsname("coo");
35
const DNSName g_groupdnsname("group");
36
const DNSName g_versiondnsname("version");
37
const DNSName g_zonesdnsname("zones");
38
39
const DNSName g_gsstsigdnsname("gss-tsig");
40
const DNSName g_hmacmd5dnsname("hmac-md5");
41
const DNSName g_hmacmd5dnsname_long("hmac-md5.sig-alg.reg.int");
42
const DNSName g_hmacsha1dnsname("hmac-sha1");
43
const DNSName g_hmacsha224dnsname("hmac-sha224");
44
const DNSName g_hmacsha256dnsname("hmac-sha256");
45
const DNSName g_hmacsha384dnsname("hmac-sha384");
46
const DNSName g_hmacsha512dnsname("hmac-sha512");
47
48
const ZoneName g_rootzonename(".");
49
50
/* raw storage
51
   in DNS label format, with trailing 0. W/o trailing 0, we are 'empty'
52
   www.powerdns.com = 3www8powerdns3com0
53
*/
54
55
std::ostream & operator<<(std::ostream &os, const DNSName& d)
56
0
{
57
0
  return os <<d.toLogString();
58
0
}
59
60
void DNSName::throwSafeRangeError(const std::string& msg, const char* buf, size_t length)
61
1.66k
{
62
1.66k
  std::string dots;
63
1.66k
  if (length > s_maxDNSNameLength) {
64
516
    length = s_maxDNSNameLength;
65
516
    dots = "...";
66
516
  }
67
1.66k
  std::string label;
68
1.66k
  DNSName::appendEscapedLabel(label, buf, length);
69
1.66k
  throw std::range_error(msg + label + dots);
70
1.66k
}
71
72
DNSName::DNSName(const std::string_view sw)
73
279k
{
74
279k
  const char* p = sw.data();
75
279k
  size_t length = sw.length();
76
77
279k
  if(length == 0 || (length == 1 && p[0]=='.')) {
78
34.1k
    d_storage.assign(1, '\0');
79
245k
  } else {
80
245k
    if(!std::memchr(p, '\\', length)) {
81
218k
      unsigned char lenpos=0;
82
218k
      unsigned char labellen=0;
83
218k
      const char* const pbegin=p, *pend=p+length;
84
85
218k
      d_storage.reserve(length+1);
86
520k
      for(auto iter = pbegin; iter != pend; ) {
87
302k
        lenpos = d_storage.size();
88
302k
        if(*iter=='.')
89
985
          throwSafeRangeError("Found . in wrong position in DNSName: ", p, length);
90
302k
        d_storage.append(1, '\0');
91
302k
        labellen=0;
92
302k
        auto begiter=iter;
93
241M
        for(; iter != pend && *iter!='.'; ++iter) {
94
240M
          labellen++;
95
240M
        }
96
302k
        d_storage.append(begiter,iter);
97
302k
        if(iter != pend)
98
93.9k
          ++iter;
99
302k
        if(labellen > s_maxDNSLabelLength) {
100
454
          throwSafeRangeError("label too long to append: ", p, length);
101
454
        }
102
103
302k
        if(iter-pbegin > static_cast<ptrdiff_t>(s_maxDNSNameLength - 1)) // reserve two bytes, one for length and one for the root label
104
105
          throwSafeRangeError("name too long to append: ", p, length);
105
106
302k
        d_storage[lenpos]=labellen;
107
302k
      }
108
218k
      d_storage.append(1, '\0');
109
218k
    }
110
27.1k
    else {
111
27.1k
      d_storage=segmentDNSNameRaw(p, length);
112
27.1k
      if(d_storage.size() > s_maxDNSNameLength) {
113
110
        throwSafeRangeError("name too long: ", p, length);
114
110
      }
115
27.1k
    }
116
245k
  }
117
279k
}
118
119
DNSName::DNSName(const char* pos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, uint16_t minOffset)
120
349k
{
121
349k
  if (offset >= len)
122
4.34k
    throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
123
124
345k
  if(!uncompress) {
125
1.79k
    if(const void * fnd=memchr(pos+offset, 0, len-offset)) {
126
1.68k
      d_storage.reserve(2+(const char*)fnd-(pos+offset));
127
1.68k
    }
128
1.79k
  }
129
130
345k
  packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset);
131
345k
}
132
133
static void checkLabelLength(uint8_t length)
134
175k
{
135
175k
  if (length == 0) {
136
0
    throw std::range_error("no such thing as an empty label to append");
137
0
  }
138
175k
  if (length > DNSName::s_maxDNSLabelLength) {
139
0
    throw std::range_error("label too long to append");
140
0
  }
141
175k
}
142
143
// this parses a DNS name until a compression pointer is found
144
size_t DNSName::parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t pos, bool uncompress)
145
365k
{
146
365k
  const size_t initialPos = pos;
147
365k
  size_t totalLength = 0;
148
365k
  unsigned char labellen = 0;
149
150
537k
  do {
151
537k
    labellen = view.at(pos);
152
537k
    ++pos;
153
154
537k
    if (labellen == 0) {
155
339k
      --pos;
156
339k
      break;
157
339k
    }
158
159
198k
    if (labellen >= 0xc0) {
160
20.9k
      if (!uncompress) {
161
228
        throw std::range_error("Found compressed label, instructed not to follow");
162
228
      }
163
20.6k
      --pos;
164
20.6k
      break;
165
20.9k
    }
166
167
177k
    if ((labellen & 0xc0) != 0) {
168
2.33k
      throw std::range_error("Found an invalid label length in qname (only one of the first two bits is set)");
169
2.33k
    }
170
175k
    checkLabelLength(labellen);
171
    // reserve one byte for the label length
172
175k
    if (totalLength + labellen > s_maxDNSNameLength - 1) {
173
179
      throw std::range_error("name too long to append");
174
179
    }
175
174k
    if (pos + labellen >= view.size()) {
176
2.05k
      throw std::range_error("Found an invalid label length in qname");
177
2.05k
    }
178
172k
    pos += labellen;
179
172k
    totalLength += 1 + labellen;
180
172k
  }
181
365k
  while (pos < view.size());
182
183
360k
  if (totalLength != 0) {
184
96.9k
    auto existingSize = d_storage.size();
185
96.9k
    if (existingSize > 0) {
186
      // remove the last label count, we are about to override it */
187
9.75k
      --existingSize;
188
9.75k
    }
189
96.9k
    d_storage.reserve(existingSize + totalLength + 1);
190
96.9k
    d_storage.resize(existingSize + totalLength);
191
96.9k
    memcpy(&d_storage.at(existingSize), &view.at(initialPos), totalLength);
192
96.9k
    d_storage.append(1, static_cast<char>(0));
193
96.9k
  }
194
360k
  return pos;
195
365k
}
196
197
// this should be the __only__ dns name parser in PowerDNS.
198
void DNSName::packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset)
199
365k
{
200
365k
  if (offset >= len) {
201
0
    throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
202
0
  }
203
204
365k
  if (offset < static_cast<size_t>(minOffset)) {
205
0
    throw std::range_error("Trying to read before the beginning of the buffer ("+std::to_string(offset)+ " < "+std::to_string(minOffset)+")");
206
0
  }
207
365k
  unsigned char labellen{0};
208
209
365k
  pdns::views::UnsignedCharView view(qpos, len);
210
365k
  auto pos = parsePacketUncompressed(view, offset, uncompress);
211
212
365k
  labellen = view.at(pos);
213
365k
  pos++;
214
365k
  if (labellen != 0 && pos < view.size()) {
215
20.3k
    if (labellen < 0xc0) {
216
0
      abort();
217
0
    }
218
219
20.3k
    if (!uncompress) {
220
0
      throw std::range_error("Found compressed label, instructed not to follow");
221
0
    }
222
223
20.3k
    labellen &= (~0xc0);
224
20.3k
    size_t newpos = (labellen << 8) + view.at(pos);
225
226
20.3k
    if (newpos >= offset) {
227
697
      throw std::range_error("Found a forward reference during label decompression");
228
697
    }
229
230
19.6k
    if (newpos < minOffset) {
231
16
      throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")");
232
16
    }
233
234
19.5k
    if (++depth > 100) {
235
0
      throw std::range_error("Abort label decompression after 100 redirects");
236
0
    }
237
238
19.5k
    packetParser(qpos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset);
239
240
19.5k
    pos++;
241
19.5k
  }
242
243
364k
  if (d_storage.empty()) {
244
253k
    d_storage.append(1, static_cast<char>(0)); // we just parsed the root
245
253k
  }
246
247
364k
  if (consumed != nullptr) {
248
338k
    *consumed = pos - offset;
249
338k
  }
250
251
364k
  if (qtype != nullptr) {
252
0
    if (pos + 2 > view.size()) {
253
0
      throw std::range_error("Trying to read qtype past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
254
0
    }
255
0
    *qtype = view.at(pos)*256 + view.at(pos+1);
256
0
  }
257
258
364k
  pos += 2;
259
364k
  if (qclass != nullptr) {
260
0
    if (pos + 2 > view.size()) {
261
0
      throw std::range_error("Trying to read qclass past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
262
0
    }
263
0
    *qclass = view.at(pos)*256 + view.at(pos+1);
264
0
  }
265
364k
}
266
267
std::string DNSName::toString(const std::string& separator, const bool trailing) const
268
39.8k
{
269
39.8k
  std::string ret;
270
39.8k
  toString(ret, separator, trailing);
271
39.8k
  return ret;
272
39.8k
}
273
274
void DNSName::toString(std::string& output, const std::string& separator, const bool trailing) const
275
39.8k
{
276
39.8k
  if (empty()) {
277
166
    throw std::out_of_range("Attempt to print an unset DNSName");
278
166
  }
279
280
39.7k
  if (isRoot()) {
281
1.70k
    output += (trailing ? separator : "");
282
1.70k
    return;
283
1.70k
  }
284
285
38.0k
  if (output.capacity() < (output.size() + d_storage.size())) {
286
6.42k
    output.reserve(output.size() + d_storage.size());
287
6.42k
  }
288
289
38.0k
  {
290
    // iterate over the raw labels
291
38.0k
    const char* p = d_storage.c_str();
292
38.0k
    const char* end = p + d_storage.size();
293
294
85.2k
    while (p < end && *p) {
295
47.2k
      appendEscapedLabel(output, p + 1, static_cast<size_t>(*p));
296
47.2k
      output += separator;
297
47.2k
      p += *p + 1;
298
47.2k
    }
299
38.0k
  }
300
301
38.0k
  if (!trailing) {
302
32.9k
    output.resize(output.size() - separator.size());
303
32.9k
  }
304
38.0k
}
305
306
std::string DNSName::toLogString() const
307
441
{
308
441
  if (empty()) {
309
75
    return "(empty)";
310
75
  }
311
312
366
  return toStringRootDot();
313
441
}
314
315
std::string DNSName::toDNSString() const
316
0
{
317
0
  if (empty()) {
318
0
    throw std::out_of_range("Attempt to DNSString an unset DNSName");
319
0
  }
320
321
0
  return std::string(d_storage.c_str(), d_storage.length());
322
0
}
323
324
std::string DNSName::toDNSStringLC() const
325
0
{
326
0
  auto result = toDNSString();
327
0
  toLowerInPlace(result); // label lengths are always < 'A'
328
0
  return result;
329
0
}
330
331
/**
332
 * Get the length of the DNSName on the wire
333
 *
334
 * @return the total wirelength of the DNSName
335
 */
336
9.87k
size_t DNSName::wirelength() const {
337
9.87k
  return d_storage.length();
338
9.87k
}
339
340
// Are WE part of parent
341
bool DNSName::isPartOf(const DNSName& parent) const
342
0
{
343
0
  if(parent.empty() || empty()) {
344
0
    throw std::out_of_range("empty DNSNames aren't part of anything");
345
0
  }
346
347
0
  if(parent.d_storage.size() > d_storage.size()) {
348
0
    return false;
349
0
  }
350
351
  // this is slightly complicated since we can't start from the end, since we can't see where a label begins/ends then
352
0
  for(auto us=d_storage.cbegin(); us<d_storage.cend(); us+=*us+1) {
353
0
    auto distance = std::distance(us,d_storage.cend());
354
0
    if (distance < 0 || static_cast<size_t>(distance) < parent.d_storage.size()) {
355
0
      break;
356
0
    }
357
0
    if (static_cast<size_t>(distance) == parent.d_storage.size()) {
358
0
      auto p = parent.d_storage.cbegin();
359
0
      for(; us != d_storage.cend(); ++us, ++p) {
360
0
        if(dns_tolower(*p) != dns_tolower(*us))
361
0
          return false;
362
0
      }
363
0
      return true;
364
0
    }
365
0
    if (static_cast<uint8_t>(*us) > s_maxDNSLabelLength) {
366
0
      throw std::out_of_range("illegal label length in DNSName");
367
0
    }
368
0
  }
369
0
  return false;
370
0
}
371
372
DNSName DNSName::makeRelative(const DNSName& zone) const
373
0
{
374
0
  DNSName ret(*this);
375
0
  ret.makeUsRelative(zone);
376
0
  return ret;
377
0
}
378
379
void DNSName::makeUsRelative(const DNSName& zone)
380
0
{
381
0
  if (isPartOf(zone)) {
382
0
    d_storage.erase(d_storage.size()-zone.d_storage.size());
383
0
    d_storage.append(1, static_cast<char>(0)); // put back the trailing 0
384
0
  }
385
0
  else {
386
0
    clear();
387
0
  }
388
0
}
389
390
DNSName DNSName::getCommonLabels(const DNSName& other) const
391
0
{
392
0
  if (empty() || other.empty()) {
393
0
    return DNSName();
394
0
  }
395
396
0
  DNSName result(g_rootdnsname);
397
398
0
  const std::vector<std::string> ours = getRawLabels();
399
0
  const std::vector<std::string> others = other.getRawLabels();
400
401
0
  for (size_t pos = 0; ours.size() > pos && others.size() > pos; pos++) {
402
0
    const std::string& ourLabel = ours.at(ours.size() - pos - 1);
403
0
    const std::string& otherLabel = others.at(others.size() - pos - 1);
404
405
0
    if (!pdns_iequals(ourLabel, otherLabel)) {
406
0
      break;
407
0
    }
408
409
0
    result.prependRawLabel(ourLabel);
410
0
  }
411
412
0
  return result;
413
0
}
414
415
DNSName DNSName::labelReverse() const
416
0
{
417
0
  DNSName ret;
418
419
0
  if (isRoot()) {
420
0
    return *this; // we don't create the root automatically below
421
0
  }
422
423
0
  if (!empty()) {
424
0
    vector<string> l=getRawLabels();
425
0
    while(!l.empty()) {
426
0
      ret.appendRawLabel(l.back());
427
0
      l.pop_back();
428
0
    }
429
0
  }
430
0
  return ret;
431
0
}
432
433
void DNSName::appendRawLabel(const std::string& label)
434
0
{
435
0
  appendRawLabel(label.c_str(), label.length());
436
0
}
437
438
void DNSName::appendRawLabel(const char* start, unsigned int length)
439
0
{
440
0
  checkLabelLength(length);
441
442
  // reserve one byte for the label length
443
0
  if (d_storage.size() + length > s_maxDNSNameLength - 1) {
444
0
    throw std::range_error("name too long to append");
445
0
  }
446
447
0
  if (d_storage.empty()) {
448
0
    d_storage.reserve(1 + length + 1);
449
0
    d_storage.append(1, static_cast<char>(length));
450
0
  }
451
0
  else {
452
0
    d_storage.reserve(d_storage.size() + length + 1);
453
0
    *d_storage.rbegin() = static_cast<char>(length);
454
0
  }
455
0
  d_storage.append(start, length);
456
0
  d_storage.append(1, static_cast<char>(0));
457
0
}
458
459
void DNSName::prependRawLabel(const std::string& label)
460
0
{
461
0
  checkLabelLength(label.size());
462
463
  // reserve one byte for the label length
464
0
  if (d_storage.size() + label.size() > s_maxDNSNameLength - 1) {
465
0
    throw std::range_error("name too long to prepend");
466
0
  }
467
468
0
  if (d_storage.empty()) {
469
0
    d_storage.reserve(1 + label.size() + 1);
470
0
    d_storage.append(1, static_cast<char>(0));
471
0
  }
472
0
  else {
473
0
    d_storage.reserve(d_storage.size() + 1 + label.size());
474
0
  }
475
476
0
  string_t prep(1, static_cast<char>(label.size()));
477
0
  prep.append(label.c_str(), label.size());
478
0
  d_storage = prep+d_storage;
479
0
}
480
481
int DNSName::slowCanonCompare_three_way(const DNSName& rhs) const
482
0
{
483
  // Unfortunately we can't use std::lexicographical_compare_three_way() yet
484
  // as this would require C++20.
485
0
  const auto ours = getRawLabels();
486
0
  const auto rhsLabels = rhs.getRawLabels();
487
0
  auto iter1 = ours.rbegin();
488
0
  const auto& last1 = ours.rend();
489
0
  auto iter2 = rhsLabels.rbegin();
490
0
  const auto& last2 = rhsLabels.rend();
491
0
  while (iter1 != last1 && iter2 != last2) {
492
0
    if (int res = pdns_ilexicographical_compare_three_way(*iter1, *iter2); res != 0) {
493
0
      return res;
494
0
    }
495
0
    ++iter1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
496
0
    ++iter2; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
497
0
  }
498
0
  if (iter1 == last1) {
499
0
    if (iter2 != last2) {
500
0
      return -1; // lt
501
0
    }
502
0
  }
503
0
  else {
504
0
    return 1; // gt
505
0
  }
506
0
  return 0; // eq
507
0
}
508
509
int DNSName::canonCompare_three_way(const DNSName& rhs, bool pretty) const
510
0
{
511
  //      01234567890abcd
512
  // us:  1a3www4ds9a2nl
513
  // rhs: 3www6online3com
514
  // to compare, we start at the back, is nl < com? no -> done
515
  //
516
  // 0,2,6,a
517
  // 0,4,a
518
519
0
  std::array<uint8_t,64> ourpos{};
520
0
  std::array<uint8_t,64> rhspos{};
521
0
  uint8_t ourcount=0;
522
0
  uint8_t rhscount=0;
523
  //cout<<"Asked to compare "<<toString()<<" to "<<rhs.toString()<<endl;
524
  // NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
525
0
  for (const auto* pos = (const unsigned char*)d_storage.c_str(); pos < (const unsigned char*)d_storage.c_str() + d_storage.size() && *pos != 0 && ourcount < ourpos.max_size(); pos+=*pos+1) {
526
0
    ourpos.at(ourcount++)=pos-(const unsigned char*)d_storage.c_str();
527
0
  }
528
0
  for (const auto* pos = (const unsigned char*)rhs.d_storage.c_str(); pos < (const unsigned char*)rhs.d_storage.c_str() + rhs.d_storage.size() && *pos != 0 && rhscount < rhspos.max_size(); pos+=*pos+1) {
529
0
    rhspos.at(rhscount++)=pos-(const unsigned char*)rhs.d_storage.c_str();
530
0
  }
531
  // NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
532
533
0
  if(ourcount == ourpos.max_size() || rhscount==rhspos.max_size()) {
534
0
    return slowCanonCompare_three_way(rhs);
535
0
  }
536
537
0
  for(;;) {
538
0
    if(ourcount == 0 && rhscount != 0) {
539
0
      return -1; // lt
540
0
    }
541
0
    if(rhscount == 0) {
542
0
      return ourcount == 0 ? 0 /* eq */ : 1 /* gt */;
543
0
    }
544
0
    ourcount--;
545
0
    rhscount--;
546
547
    // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
548
0
    const uint8_t ourlen = *(d_storage.c_str() + ourpos.at(ourcount));
549
0
    const uint8_t rhslen = *(rhs.d_storage.c_str() + rhspos.at(rhscount));
550
0
    std::string_view ourstr(d_storage.c_str() + ourpos.at(ourcount) + 1, ourlen);
551
0
    std::string_view rhsstr(rhs.d_storage.c_str() + rhspos.at(rhscount) + 1, rhslen);
552
    // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
553
554
    // If pretty ordering requested, sort numerical values, well,
555
    // numerically (i.e. 999.example.com < 1000.example.com).
556
    // Do not use for anything but human-intended output, as this breaks
557
    // the DNSSEC order.
558
0
    if (pretty) {
559
      // If both names are numerical (made of digits), then the longest one
560
      // always compares higher.
561
0
      if (ourlen != rhslen) {
562
0
        bool isNumerical{true};
563
0
        for (const auto chr : ourstr) {
564
0
          if (std::isdigit(static_cast<unsigned char>(chr)) == 0) {
565
0
            isNumerical = false;
566
0
            break;
567
0
          }
568
0
        }
569
0
        if (isNumerical) {
570
0
          for (const auto chr : rhsstr) {
571
0
            if (std::isdigit(static_cast<unsigned char>(chr)) == 0) {
572
0
              isNumerical = false;
573
0
              break;
574
0
            }
575
0
          }
576
0
        }
577
0
        if (isNumerical) {
578
0
          return ourlen < rhslen ? -1 : 1;
579
0
        }
580
0
      }
581
0
    }
582
583
0
    int res = pdns_ilexicographical_compare_three_way(ourstr, rhsstr);
584
0
    if (res != 0) {
585
0
      return res;
586
0
    }
587
0
  }
588
0
}
589
590
591
vector<std::string> DNSName::getRawLabels() const
592
0
{
593
0
  vector<std::string> ret;
594
0
  ret.reserve(countLabels());
595
  // 3www4ds9a2nl0
596
0
  for(const unsigned char* p = (const unsigned char*) d_storage.c_str(); p < ((const unsigned char*) d_storage.c_str()) + d_storage.size() && *p; p+=*p+1) {
597
0
    ret.push_back({(const char*)p+1, (size_t)*p}); // XXX FIXME
598
0
  }
599
0
  return ret;
600
0
}
601
602
std::string DNSName::getRawLabel(unsigned int pos) const
603
0
{
604
0
  unsigned int currentPos = 0;
605
0
  for(const unsigned char* p = (const unsigned char*) d_storage.c_str(); p < ((const unsigned char*) d_storage.c_str()) + d_storage.size() && *p; p+=*p+1, currentPos++) {
606
0
    if (currentPos == pos) {
607
0
      return std::string((const char*)p+1, (size_t)*p);
608
0
    }
609
0
  }
610
611
0
  throw std::out_of_range("trying to get label at position "+std::to_string(pos)+" of a DNSName that only has "+std::to_string(currentPos)+" labels");
612
0
}
613
614
DNSName DNSName::getLastLabel() const
615
0
{
616
0
  DNSName ret(*this);
617
0
  ret.trimToLabels(1);
618
0
  return ret;
619
0
}
620
621
bool DNSName::chopOff()
622
0
{
623
0
  if (d_storage.empty() || d_storage[0]==0) {
624
0
    return false;
625
0
  }
626
0
  d_storage.erase(0, (unsigned int)d_storage[0]+1);
627
0
  return true;
628
0
}
629
630
bool DNSName::isWildcard() const
631
0
{
632
0
  if (d_storage.size() < 2) {
633
0
    return false;
634
0
  }
635
0
  auto p = d_storage.begin();
636
0
  return (*p == 0x01 && *++p == '*');
637
0
}
638
639
/*
640
 * Returns true if the DNSName is a valid RFC 1123 hostname, this function uses
641
 * a regex on the string, so it is probably best not used when speed is essential.
642
 *
643
 * If allowUnderscore is set, underscore characters (`_') are allowed anywhere
644
 * a letter or a digit would have been. In particular, leading underscores are
645
 * allowed.
646
 */
647
bool DNSName::isHostname(bool allowUnderscore) const
648
0
{
649
0
  if (allowUnderscore) {
650
0
    static Regex hostNameRegexWithUnderscore = Regex("^(([A-Za-z0-9_]([A-Za-z0-9-_]*[A-Za-z0-9_])?)\\.)+$");
651
0
    return hostNameRegexWithUnderscore.match(this->toString());
652
0
  }
653
0
  static Regex hostNameRegex = Regex("^(([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)\\.)+$");
654
0
  return hostNameRegex.match(this->toString());
655
0
}
656
657
unsigned int DNSName::countLabels() const
658
0
{
659
0
  unsigned int count=0;
660
0
  const unsigned char* p = reinterpret_cast<const unsigned char*>(d_storage.c_str());
661
0
  const unsigned char* end = reinterpret_cast<const unsigned char*>(p + d_storage.size());
662
663
0
  while (p < end && *p) {
664
0
    ++count;
665
0
    p += *p + 1;
666
0
  }
667
0
  return count;
668
0
}
669
670
void DNSName::trimToLabels(unsigned int to)
671
0
{
672
0
  if (to != 0) {
673
0
    for (auto nlabels = countLabels(); nlabels > to; --nlabels) {
674
0
      chopOff();
675
0
    }
676
0
  }
677
0
  else {
678
    // If all the labels are to be removed, the result is either empty or
679
    // the root zone.
680
0
    if (!empty()) {
681
0
      d_storage = g_rootdnsname.d_storage;
682
0
    }
683
0
  }
684
0
}
685
686
size_t hash_value(DNSName const& d)
687
0
{
688
0
  return d.hash();
689
0
}
690
691
void DNSName::appendEscapedLabel(std::string& appendTo, const char* orig, size_t len)
692
48.9k
{
693
48.9k
  size_t pos = 0;
694
695
635k
  while (pos < len) {
696
586k
    auto p = static_cast<uint8_t>(orig[pos]);
697
586k
    if (p=='.') {
698
5.10k
      appendTo+="\\.";
699
5.10k
    }
700
581k
    else if (p=='\\') {
701
1.76k
      appendTo+="\\\\";
702
1.76k
    }
703
579k
    else if (p > 0x20 && p < 0x7f) {
704
434k
      appendTo.append(1, static_cast<char>(p));
705
434k
    }
706
145k
    else {
707
145k
      char buf[] = "000";
708
145k
      auto got = snprintf(buf, sizeof(buf), "%03" PRIu8, p);
709
145k
      if (got < 0 || static_cast<size_t>(got) >= sizeof(buf)) {
710
0
        throw std::runtime_error("Error, snprintf returned " + std::to_string(got) + " while escaping label " + std::string(orig, len));
711
0
      }
712
145k
      appendTo.append(1, '\\');
713
145k
      appendTo += buf;
714
145k
    }
715
586k
    ++pos;
716
586k
  }
717
48.9k
}
718
719
bool DNSName::has8bitBytes() const
720
0
{
721
0
  const auto& s = d_storage;
722
0
  string::size_type pos = 0;
723
0
  uint8_t length = s.at(pos);
724
0
  while (length > 0) {
725
0
    for (size_t idx = 0; idx < length; idx++) {
726
0
      ++pos;
727
0
      char c = s.at(pos);
728
0
      if (!((c >= 'a' && c <= 'z') ||
729
0
            (c >= 'A' && c <= 'Z') ||
730
0
            (c >= '0' && c <= '9') ||
731
0
            c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@' || c==' ' || c=='\\' || c==':')) {
732
0
        return true;
733
0
      }
734
0
    }
735
0
    ++pos;
736
0
    length = s.at(pos);
737
0
  }
738
739
0
  return false;
740
0
}
741
742
// clang-format off
743
const unsigned char dns_toupper_table[256] = {
744
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
745
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
746
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
747
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
748
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
749
  0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
750
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
751
  0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
752
  0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
753
  0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
754
  0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
755
  0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
756
  0x60, 'A',  'B',  'C',  'D',  'E',  'F',  'G',
757
  'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
758
  'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
759
  'X',  'Y',  'Z',  0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
760
  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
761
  0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
762
  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
763
  0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
764
  0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
765
  0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
766
  0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
767
  0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
768
  0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
769
  0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
770
  0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
771
  0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
772
  0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
773
  0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
774
  0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
775
  0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
776
};
777
778
const unsigned char dns_tolower_table[256] = {
779
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
780
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
781
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
782
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
783
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
784
  0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
785
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
786
  0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
787
  0x40, 'a',  'b',  'c',  'd',  'e',  'f',  'g',
788
  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
789
  'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
790
  'x',  'y',  'z',  0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
791
  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
792
  0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
793
  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
794
  0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
795
  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
796
  0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
797
  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
798
  0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
799
  0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
800
  0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
801
  0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
802
  0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
803
  0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
804
  0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
805
  0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
806
  0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
807
  0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
808
  0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
809
  0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
810
  0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
811
};
812
813
0
DNSName::RawLabelsVisitor::RawLabelsVisitor(const DNSName::string_t& storage): d_storage(storage)
814
0
{
815
0
  size_t position = 0;
816
0
  while (position < storage.size()) {
817
0
    auto labelLength = static_cast<uint8_t>(storage.at(position));
818
0
    if (labelLength == 0) {
819
0
      break;
820
0
    }
821
0
    d_labelPositions.at(d_position) = position;
822
0
    d_position++;
823
0
    position += labelLength + 1;
824
0
  }
825
0
}
826
827
DNSName::RawLabelsVisitor DNSName::getRawLabelsVisitor() const
828
0
{
829
0
  return DNSName::RawLabelsVisitor(getStorage());
830
0
}
831
832
std::string_view DNSName::RawLabelsVisitor::front() const
833
0
{
834
0
  if (d_position == 0) {
835
0
    throw std::out_of_range("trying to access the front of an empty DNSName::RawLabelsVisitor");
836
0
  }
837
0
  uint8_t length = d_storage.at(0);
838
0
  if (length == 0) {
839
0
    return std::string_view();
840
0
  }
841
0
  return std::string_view(&d_storage.at(1), length);
842
0
}
843
844
std::string_view DNSName::RawLabelsVisitor::back() const
845
0
{
846
0
  if (d_position == 0) {
847
0
    throw std::out_of_range("trying to access the back of an empty DNSName::RawLabelsVisitor");
848
0
  }
849
0
  size_t offset = d_labelPositions.at(d_position-1);
850
0
  uint8_t length = d_storage.at(offset);
851
0
  if (length == 0) {
852
0
    return std::string_view();
853
0
  }
854
0
  return std::string_view(&d_storage.at(offset + 1), length);
855
0
}
856
857
bool DNSName::RawLabelsVisitor::pop_back()
858
0
{
859
0
  if (d_position > 0) {
860
0
    d_position--;
861
0
    return true;
862
0
  }
863
0
  return false;
864
0
}
865
866
bool DNSName::RawLabelsVisitor::empty() const
867
0
{
868
0
  return d_position == 0;
869
0
}
870
871
bool DNSName::matchesUncompressedName(const std::string_view& wire_uncompressed) const
872
0
{
873
0
  if (wire_uncompressed.empty() != empty() || wire_uncompressed.size() < d_storage.size()) {
874
0
    return false;
875
0
  }
876
877
0
  return pdns_ilexicographical_compare_three_way(std::string_view(wire_uncompressed.data(), d_storage.size()), d_storage) == 0;
878
0
}
879
880
#if defined(PDNS_AUTH) // [
881
std::ostream & operator<<(std::ostream &ostr, const ZoneName& zone)
882
{
883
  return ostr << zone.toLogString();
884
}
885
886
size_t hash_value(ZoneName const& zone)
887
{
888
  return zone.hash();
889
}
890
891
// Sugar while ZoneName::operator DNSName are made explicit. These can't be
892
// made inline in class DNSName due to chicken-and-egg declaration order
893
// between DNSName and ZoneName.
894
bool DNSName::isPartOf(const ZoneName& rhs) const
895
{
896
  return isPartOf(rhs.operator const DNSName&());
897
}
898
DNSName DNSName::makeRelative(const ZoneName& zone) const
899
{
900
  return makeRelative(zone.operator const DNSName&());
901
}
902
void DNSName::makeUsRelative(const ZoneName& zone)
903
{
904
  makeUsRelative(zone.operator const DNSName&());
905
}
906
907
std::string_view::size_type ZoneName::findVariantSeparator(std::string_view name)
908
{
909
  std::string_view::size_type pos{0};
910
911
  // Try to be as fast as possible in the non-variant case and exit
912
  // quickly if we don't find two dots in a row.
913
  while ((pos = name.find('.', pos)) != std::string_view::npos) {
914
    ++pos;
915
    if (pos >= name.length()) { // trailing single dot
916
      return std::string_view::npos;
917
    }
918
    if (name.at(pos) == '.') {
919
      // We have found two dots in a row, but the first dot might have been
920
      // escaped. So we now need to count how many \ characters we can find a
921
      // row before it; if their number is odd, the first dot is escaped and
922
      // we need to keep searching.
923
      size_t slashes{0};
924
      while (pos >= 2 + slashes && name.at(pos - 2 - slashes) == '\\') {
925
        ++slashes;
926
      }
927
      if ((slashes % 2) == 0) {
928
        break;
929
      }
930
    }
931
  }
932
  return pos;
933
}
934
935
ZoneName::ZoneName(std::string_view name)
936
{
937
  if (auto sep = findVariantSeparator(name); sep != std::string_view::npos) {
938
    setVariant(name.substr(sep + 1)); // ignore leading dot in variant name
939
    name = name.substr(0, sep); // keep trailing dot in zone name
940
  }
941
  d_name = DNSName(name);
942
}
943
944
ZoneName::ZoneName(std::string_view name, std::string_view::size_type sep)
945
{
946
  if (sep != std::string_view::npos) {
947
    setVariant(name.substr(sep + 1)); // ignore leading dot in variant name
948
    name = name.substr(0, sep); // keep trailing dot in zone name
949
  }
950
  d_name = DNSName(name);
951
}
952
953
void ZoneName::setVariant(std::string_view variant)
954
{
955
  if (variant.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789_-") != std::string_view::npos) {
956
    throw std::out_of_range("invalid character in variant name '" + std::string{variant} + "'");
957
  }
958
  d_variant = variant;
959
}
960
961
std::string ZoneName::toLogString() const
962
{
963
  std::string ret = d_name.toLogString();
964
  if (!d_variant.empty()) {
965
    // Because toLogString() above uses toStringRootDot(), we do not want to
966
    // output one too many dots if this is a root-with-variant.
967
    ret.push_back('.');
968
    if (!d_name.isRoot()) {
969
      ret.push_back('.');
970
    }
971
    ret += d_variant;
972
  }
973
  return ret;
974
}
975
976
std::string ZoneName::toString(const std::string& separator, const bool trailing) const
977
{
978
  std::string ret = d_name.toString(separator, trailing);
979
  if (!d_variant.empty()) {
980
    if (!trailing) {
981
      ret.push_back('.');
982
    }
983
    ret.push_back('.');
984
    ret += d_variant;
985
  }
986
  return ret;
987
}
988
989
std::string ZoneName::toStringNoDot() const
990
{
991
  std::string ret = d_name.toStringNoDot();
992
  if (!d_variant.empty()) {
993
    ret += "..";
994
    ret += d_variant;
995
  }
996
  return ret;
997
}
998
999
std::string ZoneName::toStringRootDot() const
1000
{
1001
  std::string ret = d_name.toStringRootDot();
1002
  if (!d_variant.empty()) {
1003
    if (!d_name.isRoot()) {
1004
      ret.push_back('.');
1005
    }
1006
    ret.push_back('.');
1007
    ret += d_variant;
1008
  }
1009
  return ret;
1010
}
1011
1012
size_t ZoneName::hash(size_t init) const
1013
{
1014
  if (!d_variant.empty()) {
1015
    init = burtleCI(d_variant, init);
1016
  }
1017
1018
  return d_name.hash(init);
1019
}
1020
1021
bool ZoneName::operator<(const ZoneName& rhs)  const
1022
{
1023
  // Order by DNSName first, by variant second.
1024
  // Unfortunately we can't use std::lexicographical_compare_three_way() yet
1025
  // as this would require C++20.
1026
  auto iter1 = d_name.getStorage().rbegin();
1027
  const auto last1 = d_name.getStorage().rend();
1028
  auto iter2 = rhs.d_name.getStorage().rbegin();
1029
  const auto last2 = rhs.d_name.getStorage().rend();
1030
  while (iter1 != last1 && iter2 != last2) {
1031
    auto char1 = dns_tolower(*iter1);
1032
    auto char2 = dns_tolower(*iter2);
1033
    if (char1 < char2) {
1034
      return true;
1035
    }
1036
    if (char1 > char2) {
1037
      return false;
1038
    }
1039
    ++iter1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1040
    ++iter2; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1041
  }
1042
  if (iter1 == last1) {
1043
    if (iter2 != last2) {
1044
      return true; // our DNSName is shorter (subset) than the other
1045
    }
1046
  }
1047
  else {
1048
    return false; // our DNSName is longer (superset) than the other
1049
  }
1050
  // At this point, both DNSName compare equal, we have to compare
1051
  // variants (which are case-sensitive).
1052
  return d_variant < rhs.d_variant;
1053
}
1054
1055
int ZoneName::canonCompare_three_way(const ZoneName& rhs) const
1056
{
1057
  // Similarly to operator< above, this compares DNSName first, variant
1058
  // second.
1059
  if (int res = d_name.canonCompare_three_way(rhs.d_name); res != 0) {
1060
    return res;
1061
  }
1062
  // Both DNSName compare equal.
1063
  return d_variant.compare(rhs.d_variant);
1064
}
1065
#endif // ]