Coverage Report

Created: 2025-06-13 06:27

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