Coverage Report

Created: 2025-09-05 06:36

/src/pdns/pdns/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(".");
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
713
{
62
713
  std::string dots;
63
713
  if (length > s_maxDNSNameLength) {
64
519
    length = s_maxDNSNameLength;
65
519
    dots = "...";
66
519
  }
67
713
  std::string label;
68
713
  DNSName::appendEscapedLabel(label, buf, length);
69
713
  throw std::range_error(msg + label + dots);
70
713
}
71
72
DNSName::DNSName(const std::string_view sw)
73
209k
{
74
209k
  const char* p = sw.data();
75
209k
  size_t length = sw.length();
76
77
209k
  if(length == 0 || (length == 1 && p[0]=='.')) {
78
7.77k
    d_storage.assign(1, '\0');
79
202k
  } else {
80
202k
    if(!std::memchr(p, '\\', length)) {
81
163k
      unsigned char lenpos=0;
82
163k
      unsigned char labellen=0;
83
163k
      const char* const pbegin=p, *pend=p+length;
84
85
163k
      d_storage.reserve(length+1);
86
359k
      for(auto iter = pbegin; iter != pend; ) {
87
195k
        lenpos = d_storage.size();
88
195k
        if(*iter=='.')
89
82
          throwSafeRangeError("Found . in wrong position in DNSName: ", p, length);
90
195k
        d_storage.append(1, '\0');
91
195k
        labellen=0;
92
195k
        auto begiter=iter;
93
254M
        for(; iter != pend && *iter!='.'; ++iter) {
94
254M
          labellen++;
95
254M
        }
96
195k
        d_storage.append(begiter,iter);
97
195k
        if(iter != pend)
98
36.0k
          ++iter;
99
195k
        if(labellen > 63)
100
444
          throwSafeRangeError("label too long to append: ", p, length);
101
102
195k
        if(iter-pbegin > static_cast<ptrdiff_t>(s_maxDNSNameLength - 1)) // reserve two bytes, one for length and one for the root label
103
102
          throwSafeRangeError("name too long to append: ", p, length);
104
105
195k
        d_storage[lenpos]=labellen;
106
195k
      }
107
163k
      d_storage.append(1, '\0');
108
163k
    }
109
38.7k
    else {
110
38.7k
      d_storage=segmentDNSNameRaw(p, length);
111
38.7k
      if(d_storage.size() > s_maxDNSNameLength) {
112
72
        throwSafeRangeError("name too long: ", p, length);
113
72
      }
114
38.7k
    }
115
202k
  }
116
209k
}
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
201k
{
120
201k
  if (offset >= len)
121
2.38k
    throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
122
123
198k
  if(!uncompress) {
124
2.97k
    if(const void * fnd=memchr(pos+offset, 0, len-offset)) {
125
2.85k
      d_storage.reserve(2+(const char*)fnd-(pos+offset));
126
2.85k
    }
127
2.97k
  }
128
129
198k
  packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset);
130
198k
}
131
132
static void checkLabelLength(uint8_t length)
133
86.0k
{
134
86.0k
  if (length == 0) {
135
0
    throw std::range_error("no such thing as an empty label to append");
136
0
  }
137
86.0k
  if (length > 63) {
138
0
    throw std::range_error("label too long to append");
139
0
  }
140
86.0k
}
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
217k
{
145
217k
  const size_t initialPos = pos;
146
217k
  size_t totalLength = 0;
147
217k
  unsigned char labellen = 0;
148
149
302k
  do {
150
302k
    labellen = view.at(pos);
151
302k
    ++pos;
152
153
302k
    if (labellen == 0) {
154
196k
      --pos;
155
196k
      break;
156
196k
    }
157
158
105k
    if (labellen >= 0xc0) {
159
19.4k
      if (!uncompress) {
160
251
        throw std::range_error("Found compressed label, instructed not to follow");
161
251
      }
162
19.2k
      --pos;
163
19.2k
      break;
164
19.4k
    }
165
166
86.4k
    if ((labellen & 0xc0) != 0) {
167
495
      throw std::range_error("Found an invalid label length in qname (only one of the first two bits is set)");
168
495
    }
169
86.0k
    checkLabelLength(labellen);
170
    // reserve one byte for the label length
171
86.0k
    if (totalLength + labellen > s_maxDNSNameLength - 1) {
172
26
      throw std::range_error("name too long to append");
173
26
    }
174
85.9k
    if (pos + labellen >= view.size()) {
175
404
      throw std::range_error("Found an invalid label length in qname");
176
404
    }
177
85.5k
    pos += labellen;
178
85.5k
    totalLength += 1 + labellen;
179
85.5k
  }
180
217k
  while (pos < view.size());
181
182
216k
  if (totalLength != 0) {
183
41.9k
    auto existingSize = d_storage.size();
184
41.9k
    if (existingSize > 0) {
185
      // remove the last label count, we are about to override it */
186
5.96k
      --existingSize;
187
5.96k
    }
188
41.9k
    d_storage.reserve(existingSize + totalLength + 1);
189
41.9k
    d_storage.resize(existingSize + totalLength);
190
41.9k
    memcpy(&d_storage.at(existingSize), &view.at(initialPos), totalLength);
191
41.9k
    d_storage.append(1, static_cast<char>(0));
192
41.9k
  }
193
216k
  return pos;
194
217k
}
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
217k
{
199
217k
  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
217k
  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
217k
  unsigned char labellen{0};
207
208
217k
  pdns::views::UnsignedCharView view(qpos, len);
209
217k
  auto pos = parsePacketUncompressed(view, offset, uncompress);
210
211
217k
  labellen = view.at(pos);
212
217k
  pos++;
213
217k
  if (labellen != 0 && pos < view.size()) {
214
18.8k
    if (labellen < 0xc0) {
215
0
      abort();
216
0
    }
217
218
18.8k
    if (!uncompress) {
219
0
      throw std::range_error("Found compressed label, instructed not to follow");
220
0
    }
221
222
18.8k
    labellen &= (~0xc0);
223
18.8k
    size_t newpos = (labellen << 8) + view.at(pos);
224
225
18.8k
    if (newpos >= offset) {
226
317
      throw std::range_error("Found a forward reference during label decompression");
227
317
    }
228
229
18.5k
    if (newpos < minOffset) {
230
22
      throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")");
231
22
    }
232
233
18.5k
    if (++depth > 100) {
234
0
      throw std::range_error("Abort label decompression after 100 redirects");
235
0
    }
236
237
18.5k
    packetParser(qpos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset);
238
239
18.5k
    pos++;
240
18.5k
  }
241
242
216k
  if (d_storage.empty()) {
243
161k
    d_storage.append(1, static_cast<char>(0)); // we just parsed the root
244
161k
  }
245
246
216k
  if (consumed != nullptr) {
247
195k
    *consumed = pos - offset;
248
195k
  }
249
250
216k
  if (qtype != nullptr) {
251
1.01k
    if (pos + 2 > view.size()) {
252
61
      throw std::range_error("Trying to read qtype past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
253
61
    }
254
954
    *qtype = view.at(pos)*256 + view.at(pos+1);
255
954
  }
256
257
216k
  pos += 2;
258
216k
  if (qclass != nullptr) {
259
954
    if (pos + 2 > view.size()) {
260
17
      throw std::range_error("Trying to read qclass past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
261
17
    }
262
937
    *qclass = view.at(pos)*256 + view.at(pos+1);
263
937
  }
264
216k
}
265
266
std::string DNSName::toString(const std::string& separator, const bool trailing) const
267
29.2k
{
268
29.2k
  std::string ret;
269
29.2k
  toString(ret, separator, trailing);
270
29.2k
  return ret;
271
29.2k
}
272
273
void DNSName::toString(std::string& output, const std::string& separator, const bool trailing) const
274
29.2k
{
275
29.2k
  if (empty()) {
276
161
    throw std::out_of_range("Attempt to print an unset DNSName");
277
161
  }
278
279
29.1k
  if (isRoot()) {
280
969
    output += (trailing ? separator : "");
281
969
    return;
282
969
  }
283
284
28.1k
  if (output.capacity() < (output.size() + d_storage.size())) {
285
5.17k
    output.reserve(output.size() + d_storage.size());
286
5.17k
  }
287
288
28.1k
  {
289
    // iterate over the raw labels
290
28.1k
    const char* p = d_storage.c_str();
291
28.1k
    const char* end = p + d_storage.size();
292
293
58.4k
    while (p < end && *p) {
294
30.3k
      appendEscapedLabel(output, p + 1, static_cast<size_t>(*p));
295
30.3k
      output += separator;
296
30.3k
      p += *p + 1;
297
30.3k
    }
298
28.1k
  }
299
300
28.1k
  if (!trailing) {
301
26.7k
    output.resize(output.size() - separator.size());
302
26.7k
  }
303
28.1k
}
304
305
std::string DNSName::toLogString() const
306
444
{
307
444
  if (empty()) {
308
62
    return "(empty)";
309
62
  }
310
311
382
  return toStringRootDot();
312
444
}
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.35k
size_t DNSName::wirelength() const {
336
1.35k
  return d_storage.length();
337
1.35k
}
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
bool DNSName::isHostname() const
643
0
{
644
0
  static Regex hostNameRegex = Regex("^(([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)\\.)+$");
645
0
  return hostNameRegex.match(this->toString());
646
0
}
647
648
unsigned int DNSName::countLabels() const
649
0
{
650
0
  unsigned int count=0;
651
0
  const unsigned char* p = reinterpret_cast<const unsigned char*>(d_storage.c_str());
652
0
  const unsigned char* end = reinterpret_cast<const unsigned char*>(p + d_storage.size());
653
654
0
  while (p < end && *p) {
655
0
    ++count;
656
0
    p += *p + 1;
657
0
  }
658
0
  return count;
659
0
}
660
661
void DNSName::trimToLabels(unsigned int to)
662
0
{
663
0
  for (auto nlabels = countLabels(); nlabels > to; --nlabels) {
664
0
    chopOff();
665
0
  }
666
0
}
667
668
669
size_t hash_value(DNSName const& d)
670
0
{
671
0
  return d.hash();
672
0
}
673
674
void DNSName::appendEscapedLabel(std::string& appendTo, const char* orig, size_t len)
675
31.0k
{
676
31.0k
  size_t pos = 0;
677
678
426k
  while (pos < len) {
679
395k
    auto p = static_cast<uint8_t>(orig[pos]);
680
395k
    if (p=='.') {
681
1.00k
      appendTo+="\\.";
682
1.00k
    }
683
394k
    else if (p=='\\') {
684
488
      appendTo+="\\\\";
685
488
    }
686
394k
    else if (p > 0x20 && p < 0x7f) {
687
333k
      appendTo.append(1, static_cast<char>(p));
688
333k
    }
689
60.3k
    else {
690
60.3k
      char buf[] = "000";
691
60.3k
      auto got = snprintf(buf, sizeof(buf), "%03" PRIu8, p);
692
60.3k
      if (got < 0 || static_cast<size_t>(got) >= sizeof(buf)) {
693
0
        throw std::runtime_error("Error, snprintf returned " + std::to_string(got) + " while escaping label " + std::string(orig, len));
694
0
      }
695
60.3k
      appendTo.append(1, '\\');
696
60.3k
      appendTo += buf;
697
60.3k
    }
698
395k
    ++pos;
699
395k
  }
700
31.0k
}
701
702
bool DNSName::has8bitBytes() const
703
0
{
704
0
  const auto& s = d_storage;
705
0
  string::size_type pos = 0;
706
0
  uint8_t length = s.at(pos);
707
0
  while (length > 0) {
708
0
    for (size_t idx = 0; idx < length; idx++) {
709
0
      ++pos;
710
0
      char c = s.at(pos);
711
0
      if (!((c >= 'a' && c <= 'z') ||
712
0
            (c >= 'A' && c <= 'Z') ||
713
0
            (c >= '0' && c <= '9') ||
714
0
            c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@' || c==' ' || c=='\\' || c==':')) {
715
0
        return true;
716
0
      }
717
0
    }
718
0
    ++pos;
719
0
    length = s.at(pos);
720
0
  }
721
722
0
  return false;
723
0
}
724
725
// clang-format off
726
const unsigned char dns_toupper_table[256] = {
727
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
728
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
729
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
730
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
731
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
732
  0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
733
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
734
  0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
735
  0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
736
  0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
737
  0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
738
  0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
739
  0x60, 'A',  'B',  'C',  'D',  'E',  'F',  'G',
740
  'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
741
  'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
742
  'X',  'Y',  'Z',  0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
743
  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
744
  0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
745
  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
746
  0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
747
  0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
748
  0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
749
  0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
750
  0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
751
  0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
752
  0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
753
  0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
754
  0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
755
  0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
756
  0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
757
  0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
758
  0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
759
};
760
761
const unsigned char dns_tolower_table[256] = {
762
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
763
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
764
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
765
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
766
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
767
  0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
768
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
769
  0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
770
  0x40, 'a',  'b',  'c',  'd',  'e',  'f',  'g',
771
  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
772
  'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
773
  'x',  'y',  'z',  0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
774
  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
775
  0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
776
  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
777
  0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
778
  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
779
  0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
780
  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
781
  0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
782
  0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
783
  0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
784
  0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
785
  0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
786
  0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
787
  0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
788
  0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
789
  0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
790
  0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
791
  0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
792
  0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
793
  0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
794
};
795
796
0
DNSName::RawLabelsVisitor::RawLabelsVisitor(const DNSName::string_t& storage): d_storage(storage)
797
0
{
798
0
  size_t position = 0;
799
0
  while (position < storage.size()) {
800
0
    auto labelLength = static_cast<uint8_t>(storage.at(position));
801
0
    if (labelLength == 0) {
802
0
      break;
803
0
    }
804
0
    d_labelPositions.at(d_position) = position;
805
0
    d_position++;
806
0
    position += labelLength + 1;
807
0
  }
808
0
}
809
810
DNSName::RawLabelsVisitor DNSName::getRawLabelsVisitor() const
811
0
{
812
0
  return DNSName::RawLabelsVisitor(getStorage());
813
0
}
814
815
std::string_view DNSName::RawLabelsVisitor::front() const
816
0
{
817
0
  if (d_position == 0) {
818
0
    throw std::out_of_range("trying to access the front of an empty DNSName::RawLabelsVisitor");
819
0
  }
820
0
  uint8_t length = d_storage.at(0);
821
0
  if (length == 0) {
822
0
    return std::string_view();
823
0
  }
824
0
  return std::string_view(&d_storage.at(1), length);
825
0
}
826
827
std::string_view DNSName::RawLabelsVisitor::back() const
828
0
{
829
0
  if (d_position == 0) {
830
0
    throw std::out_of_range("trying to access the back of an empty DNSName::RawLabelsVisitor");
831
0
  }
832
0
  size_t offset = d_labelPositions.at(d_position-1);
833
0
  uint8_t length = d_storage.at(offset);
834
0
  if (length == 0) {
835
0
    return std::string_view();
836
0
  }
837
0
  return std::string_view(&d_storage.at(offset + 1), length);
838
0
}
839
840
bool DNSName::RawLabelsVisitor::pop_back()
841
0
{
842
0
  if (d_position > 0) {
843
0
    d_position--;
844
0
    return true;
845
0
  }
846
0
  return false;
847
0
}
848
849
bool DNSName::RawLabelsVisitor::empty() const
850
0
{
851
0
  return d_position == 0;
852
0
}
853
854
bool DNSName::matchesUncompressedName(const std::string_view& wire_uncompressed) const
855
0
{
856
0
  if (wire_uncompressed.empty() != empty() || wire_uncompressed.size() < d_storage.size()) {
857
0
    return false;
858
0
  }
859
860
0
  return pdns_ilexicographical_compare_three_way(std::string_view(wire_uncompressed.data(), d_storage.size()), d_storage) == 0;
861
0
}
862
863
#if defined(PDNS_AUTH) // [
864
std::ostream & operator<<(std::ostream &ostr, const ZoneName& zone)
865
0
{
866
0
  return ostr << zone.toLogString();
867
0
}
868
869
size_t hash_value(ZoneName const& zone)
870
0
{
871
0
  return zone.hash();
872
0
}
873
874
// Sugar while ZoneName::operator DNSName are made explicit. These can't be
875
// made inline in class DNSName due to chicken-and-egg declaration order
876
// between DNSName and ZoneName.
877
bool DNSName::isPartOf(const ZoneName& rhs) const
878
0
{
879
0
  return isPartOf(rhs.operator const DNSName&());
880
0
}
881
DNSName DNSName::makeRelative(const ZoneName& zone) const
882
0
{
883
0
  return makeRelative(zone.operator const DNSName&());
884
0
}
885
void DNSName::makeUsRelative(const ZoneName& zone)
886
0
{
887
0
  makeUsRelative(zone.operator const DNSName&());
888
0
}
889
890
std::string_view::size_type ZoneName::findVariantSeparator(std::string_view name)
891
34.5k
{
892
34.5k
  std::string_view::size_type pos{0};
893
894
  // Try to be as fast as possible in the non-variant case and exit
895
  // quickly if we don't find two dots in a row.
896
35.7k
  while ((pos = name.find('.', pos)) != std::string_view::npos) {
897
4.19k
    ++pos;
898
4.19k
    if (pos >= name.length()) { // trailing single dot
899
582
      return std::string_view::npos;
900
582
    }
901
3.60k
    if (name.at(pos) == '.') {
902
      // We have found two dots in a row, but the first dot might have been
903
      // escaped. So we now need to count how many \ characters we can find a
904
      // row before it; if their number is odd, the first dot is escaped and
905
      // we need to keep searching.
906
2.64k
      size_t slashes{0};
907
2.95k
      while (pos >= 2 + slashes && name.at(pos - 2 - slashes) == '\\') {
908
309
        ++slashes;
909
309
      }
910
2.64k
      if ((slashes % 2) == 0) {
911
2.37k
  break;
912
2.37k
      }
913
2.64k
    }
914
3.60k
  }
915
33.9k
  return pos;
916
34.5k
}
917
918
ZoneName::ZoneName(std::string_view name)
919
34.5k
{
920
34.5k
  if (auto sep = findVariantSeparator(name); sep != std::string_view::npos) {
921
2.37k
    setVariant(name.substr(sep + 1)); // ignore leading dot in variant name
922
2.37k
    name = name.substr(0, sep); // keep trailing dot in zone name
923
2.37k
  }
924
34.5k
  d_name = DNSName(name);
925
34.5k
}
926
927
ZoneName::ZoneName(std::string_view name, std::string_view::size_type sep)
928
0
{
929
0
  if (sep != std::string_view::npos) {
930
0
    setVariant(name.substr(sep + 1)); // ignore leading dot in variant name
931
0
    name = name.substr(0, sep); // keep trailing dot in zone name
932
0
  }
933
0
  d_name = DNSName(name);
934
0
}
935
936
void ZoneName::setVariant(std::string_view variant)
937
2.37k
{
938
2.37k
  if (variant.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789_-") != std::string_view::npos) {
939
88
    throw std::out_of_range("invalid character in variant name '" + std::string{variant} + "'");
940
88
  }
941
2.28k
  d_variant = variant;
942
2.28k
}
943
944
std::string ZoneName::toLogString() const
945
0
{
946
0
  std::string ret = d_name.toLogString();
947
0
  if (!d_variant.empty()) {
948
    // Because toLogString() above uses toStringRootDot(), we do not want to
949
    // output one too many dots if this is a root-with-variant.
950
0
    ret.push_back('.');
951
0
    if (!d_name.isRoot()) {
952
0
      ret.push_back('.');
953
0
    }
954
0
    ret += d_variant;
955
0
  }
956
0
  return ret;
957
0
}
958
959
std::string ZoneName::toString(const std::string& separator, const bool trailing) const
960
0
{
961
0
  std::string ret = d_name.toString(separator, trailing);
962
0
  if (!d_variant.empty()) {
963
0
    if (!trailing) {
964
0
      ret.push_back('.');
965
0
    }
966
0
    ret.push_back('.');
967
0
    ret += d_variant;
968
0
  }
969
0
  return ret;
970
0
}
971
972
std::string ZoneName::toStringNoDot() const
973
0
{
974
0
  std::string ret = d_name.toStringNoDot();
975
0
  if (!d_variant.empty()) {
976
0
    ret += "..";
977
0
    ret += d_variant;
978
0
  }
979
0
  return ret;
980
0
}
981
982
std::string ZoneName::toStringRootDot() const
983
0
{
984
0
  std::string ret = d_name.toStringRootDot();
985
0
  if (!d_variant.empty()) {
986
0
    if (!d_name.isRoot()) {
987
0
      ret.push_back('.');
988
0
    }
989
0
    ret.push_back('.');
990
0
    ret += d_variant;
991
0
  }
992
0
  return ret;
993
0
}
994
995
size_t ZoneName::hash(size_t init) const
996
0
{
997
0
  if (!d_variant.empty()) {
998
0
    init = burtleCI(d_variant, init);
999
0
  }
1000
1001
0
  return d_name.hash(init);
1002
0
}
1003
1004
bool ZoneName::operator<(const ZoneName& rhs)  const
1005
0
{
1006
  // Order by DNSName first, by variant second.
1007
  // Unfortunately we can't use std::lexicographical_compare_three_way() yet
1008
  // as this would require C++20.
1009
0
  auto iter1 = d_name.getStorage().rbegin();
1010
0
  const auto last1 = d_name.getStorage().rend();
1011
0
  auto iter2 = rhs.d_name.getStorage().rbegin();
1012
0
  const auto last2 = rhs.d_name.getStorage().rend();
1013
0
  while (iter1 != last1 && iter2 != last2) {
1014
0
    auto char1 = dns_tolower(*iter1);
1015
0
    auto char2 = dns_tolower(*iter2);
1016
0
    if (char1 < char2) {
1017
0
      return true;
1018
0
    }
1019
0
    if (char1 > char2) {
1020
0
      return false;
1021
0
    }
1022
0
    ++iter1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1023
0
    ++iter2; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1024
0
  }
1025
0
  if (iter1 == last1) {
1026
0
    if (iter2 != last2) {
1027
0
      return true; // our DNSName is shorter (subset) than the other
1028
0
    }
1029
0
  }
1030
0
  else {
1031
0
    return false; // our DNSName is longer (superset) than the other
1032
0
  }
1033
  // At this point, both DNSName compare equal, we have to compare
1034
  // variants (which are case-sensitive).
1035
0
  return d_variant < rhs.d_variant;
1036
0
}
1037
1038
int ZoneName::canonCompare_three_way(const ZoneName& rhs) const
1039
0
{
1040
  // Similarly to operator< above, this compares DNSName first, variant
1041
  // second.
1042
0
  if (int res = d_name.canonCompare_three_way(rhs.d_name); res != 0) {
1043
0
    return res;
1044
0
  }
1045
  // Both DNSName compare equal.
1046
0
  return d_variant.compare(rhs.d_variant);
1047
0
}
1048
#endif // ]