Coverage Report

Created: 2026-06-09 07:00

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