Coverage Report

Created: 2026-09-03 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/rcpgenerator.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
#ifdef HAVE_CONFIG_H
23
#include "config.h"
24
#endif
25
#include "rcpgenerator.hh"
26
#include "dnsparser.hh"
27
#include "misc.hh"
28
#include "utility.hh"
29
#include <boost/algorithm/string.hpp>
30
#include <boost/algorithm/string/classification.hpp>
31
#include <boost/algorithm/string/replace.hpp>
32
#include <boost/format.hpp>
33
34
#include <iostream>
35
#include "base32.hh"
36
#include "base64.hh"
37
#include "namespaces.hh"
38
39
RecordTextReader::RecordTextReader(string str, ZoneName zone) :
40
7.47k
  d_string(std::move(str)), d_zone(std::move(zone))
41
7.47k
{
42
   /* remove whitespace */
43
7.47k
   if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) ))
44
370
     boost::trim_if(d_string, dns_isspace);
45
7.47k
   d_end = d_string.size();
46
7.47k
}
47
48
void RecordTextReader::xfr48BitInt(uint64_t &val)
49
0
{
50
0
  auto oldpos = d_pos;
51
0
  xfr64BitInt(val);
52
0
  if (val >= (1ULL << 48)) {
53
0
    throw RecordTextException("Numerical value " + d_string.substr(oldpos, d_pos - oldpos) + " at position " + std::to_string(oldpos) + " is too large for a 48-bit integer");
54
0
  }
55
0
}
56
57
138
void RecordTextReader::xfrNodeOrLocatorID(NodeOrLocatorID& val) {
58
138
  skipSpaces();
59
138
  size_t len;
60
138
  for(len=0;
61
308k
      d_pos+len < d_string.length() && (isxdigit(static_cast<unsigned char>(d_string.at(d_pos+len))) != 0 || d_string.at(d_pos+len) == ':');
62
308k
      len++) ;   // find length of ID
63
64
  // Parse as v6, and then strip the final 64 zero bytes
65
138
  struct in6_addr tmpbuf;
66
138
  string to_parse = d_string.substr(d_pos, len) + ":0:0:0:0";
67
68
138
  if (inet_pton(AF_INET6, to_parse.c_str(), &tmpbuf) != 1) {
69
121
    throw RecordTextException("while parsing colon-delimited 64-bit field: '" + d_string.substr(d_pos, len) + "' is invalid");
70
121
  }
71
72
17
  std::memcpy(&val.content, tmpbuf.s6_addr, sizeof(val.content));
73
17
  d_pos += len;
74
17
}
75
76
void RecordTextReader::xfr64BitInt(uint64_t &val)
77
356
{
78
356
  skipSpaces();
79
80
356
  if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) == 0) {
81
28
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
82
28
  }
83
328
  size_t pos;
84
328
  val=std::stoull(d_string.substr(d_pos), &pos);
85
86
328
  d_pos += pos;
87
328
}
88
89
90
void RecordTextReader::xfr32BitInt(uint32_t &val)
91
6.53k
{
92
6.53k
  skipSpaces();
93
94
6.53k
  if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) == 0) {
95
583
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
96
583
  }
97
5.95k
  size_t pos;
98
5.95k
  val = pdns::checked_stoi<uint32_t>(d_string.c_str() + d_pos, &pos);
99
100
5.95k
  d_pos += pos;
101
5.95k
}
102
103
void RecordTextReader::xfrTime(uint32_t &val)
104
356
{
105
356
  struct tm tm;
106
356
  memset(&tm, 0, sizeof(tm));
107
108
356
  uint64_t itmp;
109
356
  xfr64BitInt(itmp);
110
111
356
  if (itmp <= (uint32_t)~0) {
112
    // formatted as seconds since epoch, not as YYYYMMDDHHmmSS:
113
111
    val = (uint32_t) itmp;
114
111
    return;
115
111
  }
116
117
245
  ostringstream tmp;
118
119
245
  tmp<<itmp;
120
121
245
  if (sscanf(tmp.str().c_str(), "%04d%02d%02d" "%02d%02d%02d",
122
245
             &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
123
245
             &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
124
6
    throw RecordTextException("unable to parse '"+std::to_string(itmp)+"' into a valid time at position "+std::to_string(d_pos)+" in '"+d_string+"'");
125
6
  }
126
127
  // Note tm_mon is still in 1..12 range at this point
128
239
  if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || tm.tm_min > 59 ||
129
90
      tm.tm_hour < 0 || tm.tm_hour > 23 || tm.tm_mday < 0 || tm.tm_mday > 31 ||
130
77
      tm.tm_mon < 1 || tm.tm_mon > 12) {
131
77
    throw RecordTextException("invalid time specification '"+std::to_string(itmp)+"' at position "+std::to_string(d_pos)+" in '"+d_string+"'");
132
77
  }
133
134
162
  tm.tm_year-=1900;
135
162
  tm.tm_mon-=1;
136
  // coverity[store_truncates_time_t]
137
162
  val=(uint32_t)Utility::timegm(&tm);
138
162
}
139
140
void RecordTextReader::xfrIP(uint32_t &val)
141
109
{
142
109
  skipSpaces();
143
144
109
  if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) == 0) {
145
25
    throw RecordTextException("while parsing IP address, expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
146
25
  }
147
84
  uint32_t octet=0;
148
84
  val=0;
149
84
  char count=0;
150
84
  bool last_was_digit = false;
151
152
658
  for(;;) {
153
658
    if(d_string.at(d_pos)=='.') {
154
57
      if (!last_was_digit)
155
2
        throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
156
55
      last_was_digit = false;
157
55
      val<<=8;
158
55
      val+=octet;
159
55
      octet=0;
160
55
      count++;
161
55
      if(count > 3)
162
1
        throw RecordTextException(string("unable to parse IP address, too many dots"));
163
55
    }
164
601
    else if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) != 0) {
165
568
      last_was_digit = true;
166
568
      octet*=10;
167
568
      octet+=d_string.at(d_pos) - '0';
168
568
      if(octet > 255)
169
7
        throw RecordTextException("unable to parse IP address");
170
568
    }
171
33
    else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',')
172
10
      break;
173
23
    else {
174
23
      throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
175
23
    }
176
615
    d_pos++;
177
615
    if(d_pos == d_string.length())
178
33
      break;
179
615
  }
180
51
  if (count != 3)
181
29
    throw RecordTextException(string("unable to parse IP address, not enough dots"));
182
22
  if (!last_was_digit)
183
1
    throw RecordTextException(string("unable to parse IP address, trailing dot"));
184
21
  val<<=8;
185
21
  val+=octet;
186
21
  val=ntohl(val);
187
21
}
188
189
190
void RecordTextReader::xfrIP6(std::string &val)
191
129
{
192
129
  struct in6_addr tmpbuf;
193
194
129
  skipSpaces();
195
196
129
  size_t len;
197
  // lookup end of value - think of ::ffff encoding too, has dots in it!
198
129
  for(len=0;
199
271k
      d_pos+len < d_string.length() && (isxdigit(static_cast<unsigned char>(d_string.at(d_pos+len))) != 0 || d_string.at(d_pos+len) == ':' || d_string.at(d_pos+len)=='.');
200
271k
    len++);
201
202
129
  if(!len)
203
14
    throw RecordTextException("while parsing IPv6 address, expected xdigits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
204
205
  // end of value is here, try parse as IPv6
206
115
  string address=d_string.substr(d_pos, len);
207
208
115
  if (inet_pton(AF_INET6, address.c_str(), &tmpbuf) != 1) {
209
100
    throw RecordTextException("while parsing IPv6 address: '" + address + "' is invalid");
210
100
  }
211
212
15
  val = std::string((char*)tmpbuf.s6_addr, 16);
213
214
15
  d_pos += len;
215
15
}
216
217
void RecordTextReader::xfrCAWithoutPort(uint8_t version, ComboAddress &val)
218
0
{
219
0
  if (version == 4) {
220
0
    uint32_t ip;
221
0
    xfrIP(ip);
222
0
    val = makeComboAddressFromRaw(4, string((const char*) &ip, 4));
223
0
  }
224
0
  else if (version == 6) {
225
0
    string ip;
226
0
    xfrIP6(ip);
227
0
    val = makeComboAddressFromRaw(6, ip);
228
0
  }
229
0
  else throw RecordTextException("invalid address family");
230
0
}
231
232
void RecordTextReader::xfrCAPort(ComboAddress &val)
233
0
{
234
0
  uint16_t port;
235
0
  xfr16BitInt(port);
236
0
  val.sin4.sin_port = port;
237
0
}
238
239
bool RecordTextReader::eof() const
240
8.39k
{
241
8.39k
  return d_pos==d_end;
242
8.39k
}
243
244
void RecordTextReader::xfr16BitInt(uint16_t &val)
245
4.13k
{
246
4.13k
  auto oldpos = d_pos;
247
4.13k
  uint32_t tmp{0};
248
4.13k
  xfr32BitInt(tmp);
249
4.13k
  val=tmp;
250
4.13k
  if(val!=tmp) {
251
32
    throw RecordTextException("Numerical value " + d_string.substr(oldpos, d_pos - oldpos) + " at position " + std::to_string(oldpos) + " is too large for a 16-bit integer");
252
32
  }
253
4.13k
}
254
255
void RecordTextReader::xfr8BitInt(uint8_t &val)
256
1.93k
{
257
1.93k
  auto oldpos = d_pos;
258
1.93k
  uint32_t tmp{0};
259
1.93k
  xfr32BitInt(tmp);
260
1.93k
  val=tmp;
261
1.93k
  if(val!=tmp) {
262
46
    throw RecordTextException("Numerical value " + d_string.substr(oldpos, d_pos - oldpos) + " at position " + std::to_string(oldpos) + " is too large for a 8-bit integer");
263
46
  }
264
1.93k
}
265
266
// this code should leave all the escapes around
267
void RecordTextReader::xfrName(DNSName& val, [[maybe_unused]] bool compress)
268
4.48k
{
269
4.48k
  skipSpaces();
270
4.48k
  DNSName sval;
271
272
4.48k
  string::size_type begin_pos = d_pos;
273
2.05M
  while (d_pos < d_end) {
274
2.05M
    if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
275
3.24k
      break;
276
3.24k
    }
277
278
2.04M
    d_pos++;
279
2.04M
  }
280
281
4.48k
  {
282
4.48k
    std::string_view view(d_string);
283
4.48k
    sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
284
4.48k
  }
285
286
4.48k
  if (sval.empty()) {
287
0
    sval = DNSName(d_zone);
288
0
  }
289
4.48k
  else if (!d_zone.empty()) {
290
3.47k
    sval += DNSName(d_zone);
291
3.47k
  }
292
4.48k
  val = std::move(sval);
293
4.48k
}
294
295
static bool isbase64(char c, bool acceptspace)
296
827k
{
297
827k
  if(dns_isspace(c))
298
6.58k
    return acceptspace;
299
821k
  if(c >= '0' && c <= '9')
300
520k
    return true;
301
300k
  if(c >= 'a' && c <= 'z')
302
138k
    return true;
303
162k
  if(c >= 'A' && c <= 'Z')
304
89.6k
    return true;
305
72.4k
  if(c=='+' || c=='/' || c=='=')
306
72.2k
    return true;
307
194
  return false;
308
72.4k
}
309
310
void RecordTextReader::xfrBlobNoSpaces(string& val, int len)
311
218
{
312
218
  skipSpaces();
313
218
  auto pos = d_pos;
314
218
  const char* strptr=d_string.c_str();
315
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
316
115k
  while(d_pos < d_end && isbase64(strptr[d_pos], false)) {
317
115k
    d_pos++;
318
115k
  }
319
320
218
  string tmp;
321
218
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
322
218
  boost::erase_all(tmp," ");
323
218
  val.clear();
324
218
  B64Decode(tmp, val);
325
326
218
  if (len>-1 && val.size() != static_cast<size_t>(len))
327
0
    throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len));
328
218
}
329
330
void RecordTextReader::xfrBlob(string& val, int len)
331
355
{
332
355
  skipSpaces();
333
355
  auto pos = d_pos;
334
355
  const char* strptr=d_string.c_str();
335
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
336
712k
  while(d_pos < d_end && isbase64(strptr[d_pos], true)) {
337
712k
    d_pos++;
338
712k
  }
339
340
355
  string tmp;
341
355
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
342
355
  boost::erase_all(tmp," ");
343
355
  val.clear();
344
355
  B64Decode(tmp, val);
345
346
355
  if (len>-1 && val.size() != static_cast<size_t>(len)) {
347
0
    throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len));
348
0
  }
349
355
}
350
351
3.69k
void RecordTextReader::xfrRFC1035CharString(string &val) {
352
3.69k
  auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val);
353
3.69k
  d_pos += ctr;
354
3.69k
}
355
356
1.13k
void RecordTextReader::xfrSVCBValueList(vector<string> &val) {
357
1.13k
  auto ctr = parseSVCBValueList(d_string.substr(d_pos, d_end - d_pos), val);
358
1.13k
  d_pos += ctr;
359
1.13k
}
360
361
void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val) // NOLINT(readability-function-cognitive-complexity)
362
2.65k
{
363
2.65k
  set<SvcParam::SvcParamKey> seenKeys;
364
7.13k
  while (d_pos != d_end) {
365
5.72k
    skipSpaces();
366
5.72k
    if (d_pos == d_end)
367
0
      return;
368
369
    // Find the SvcParamKey
370
5.72k
    auto pos = d_pos;
371
1.25M
    while (d_pos != d_end) {
372
1.25M
      if (d_string.at(d_pos) == '=' || d_string.at(d_pos) == ' ') {
373
5.17k
        break;
374
5.17k
      }
375
1.25M
      d_pos++;
376
1.25M
    }
377
378
    // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
379
5.72k
    string k = d_string.substr(pos, d_pos - pos);
380
5.72k
    SvcParam::SvcParamKey key;
381
5.72k
    bool generic;
382
5.72k
    try {
383
5.72k
      key = SvcParam::keyFromString(k, generic);
384
5.72k
    } catch (const std::invalid_argument &e) {
385
394
      throw RecordTextException(e.what());
386
394
    }
387
388
5.33k
    if (!seenKeys.insert(key).second) {
389
13
      throw RecordTextException("SvcParamKey '" + k + "' appears more than once");
390
13
    }
391
392
5.32k
    if (d_pos != d_end && d_string.at(d_pos) == '=') {
393
3.23k
      d_pos++; // Now on the first character after '='
394
3.23k
      if (d_pos == d_end || d_string.at(d_pos) == ' ') {
395
3
        throw RecordTextException("expected value after " + k + "=");
396
3
      }
397
3.23k
    }
398
399
5.31k
    switch (key) {
400
157
    case SvcParam::no_default_alpn:
401
266
    case SvcParam::ohttp:
402
266
      if (d_pos != d_end && d_string.at(d_pos) != ' ') {
403
30
        throw RecordTextException(k + " key can not have values");
404
30
      }
405
236
      val.insert(SvcParam(key));
406
236
      break;
407
275
    case SvcParam::ipv4hint: /* fall-through */
408
563
    case SvcParam::ipv6hint: {
409
563
      vector<ComboAddress> hints;
410
563
      bool doAuto{false};
411
563
      if (generic) {
412
202
        string value;
413
202
        xfrRFC1035CharString(value);
414
202
        size_t len = key == SvcParam::ipv4hint ? 4 : 16;
415
202
        if (value.empty()) {
416
34
          throw RecordTextException("value is required for SVC Param " + k);
417
34
        }
418
168
        if (value.size() % len != 0) {
419
38
          throw RecordTextException(k + " in generic format has wrong number of bytes");
420
38
        }
421
120k
        for (size_t i=0; i<value.size(); i += len) {
422
120k
          auto hint = makeComboAddressFromRaw(static_cast<uint8_t>(key), &value.at(i), len);
423
120k
          hints.push_back(hint);
424
120k
        }
425
361
      } else {
426
361
        vector<string> value;
427
361
        xfrSVCBValueList(value);
428
250k
        for (auto const &v: value) {
429
250k
          if (v == "auto") {
430
26
            doAuto = true;
431
26
            hints.clear();
432
26
            break;
433
26
          }
434
250k
          hints.push_back(ComboAddress(v));
435
250k
        }
436
361
      }
437
491
      if (!doAuto && hints.empty()) {
438
5
        throw RecordTextException("value is required for SVC Param " + k);
439
5
      }
440
486
      try {
441
486
        auto p = SvcParam(key, std::move(hints));
442
486
        p.setAutoHint(doAuto);
443
486
        val.insert(std::move(p));
444
486
      }
445
486
      catch (const std::invalid_argument& e) {
446
30
        throw RecordTextException(e.what());
447
30
      }
448
414
      break;
449
486
    }
450
533
    case SvcParam::alpn: {
451
533
      vector<string> value;
452
533
      if (generic) {
453
160
        string v;
454
160
        xfrRFC1035CharString(v);
455
160
        size_t spos{0}, len;
456
102k
        while (spos < v.length()) {
457
102k
          len = v.at(spos);
458
102k
          spos += 1;
459
102k
          if (len == 0) {
460
3
            throw RecordTextException("ALPN values cannot be empty strings");
461
3
          }
462
102k
          if (len > 255) {
463
3
            throw RecordTextException("Length of ALPN value goes over 255");
464
3
          }
465
102k
          if (len > v.length() - spos) {
466
38
            throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
467
38
          }
468
102k
          value.push_back(v.substr(spos, len));
469
102k
          spos += len;
470
102k
        }
471
373
      } else {
472
373
        xfrSVCBValueList(value);
473
193k
        for (const auto& item : value) {
474
193k
          if (item.length() > 255) {
475
1
            throw RecordTextException("Length of SVC value goes over 255");
476
1
          }
477
193k
        }
478
373
      }
479
488
      if (value.empty()) {
480
11
        throw RecordTextException("value is required for SVC Param " + k);
481
11
      }
482
290k
      for (const auto &alpn_value : value) {
483
290k
        if (alpn_value.empty()) {
484
0
          throw RecordTextException("ALPN values cannot be empty strings");
485
0
        }
486
290k
      }
487
477
      val.insert(SvcParam(key, std::move(value)));
488
477
      break;
489
477
    }
490
732
    case SvcParam::mandatory: {
491
732
      if (generic) {
492
329
        string v;
493
329
        xfrRFC1035CharString(v);
494
329
        if (v.empty()) {
495
50
          throw RecordTextException("value is required for SVC Param " + k);
496
50
        }
497
279
        if (v.length() % 2 != 0) {
498
35
          throw RecordTextException("Wrong number of bytes in SVC Param " + k);
499
35
        }
500
244
        std::set<SvcParam::SvcParamKey> keys;
501
15.5k
        for (size_t i=0; i < v.length(); i += 2) {
502
15.3k
          uint16_t mand = (static_cast<uint8_t>(v.at(i)) << 8);
503
15.3k
          mand += static_cast<uint8_t>(v.at(i+1));
504
15.3k
          keys.insert(SvcParam::SvcParamKey(mand));
505
15.3k
        }
506
244
        val.insert(SvcParam(key, std::move(keys)));
507
244
        break;
508
279
      }
509
403
      vector<string> parts;
510
403
      xfrSVCBValueList(parts);
511
403
      if (parts.empty()) {
512
3
        throw RecordTextException("value is required for SVC Param " + k);
513
3
      }
514
400
      set<string> values(parts.begin(), parts.end());
515
400
      val.insert(SvcParam(key, std::move(values)));
516
400
      break;
517
403
    }
518
130
    case SvcParam::port: {
519
130
      uint16_t port;
520
130
      if (generic) {
521
109
        string v;
522
109
        xfrRFC1035CharString(v);
523
109
        if (v.empty()) {
524
7
          throw RecordTextException("value is required for SVC Param " + k);
525
7
        }
526
102
        if (v.length() != 2) {
527
22
          throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v.length()));
528
22
        }
529
80
        port = static_cast<uint8_t>(v.at(0)) << 8;
530
80
        port += static_cast<uint8_t>(v.at(1));
531
80
      } else {
532
21
        string portstring;
533
21
        xfrRFC1035CharString(portstring);
534
21
        if (portstring.empty()) {
535
2
          throw RecordTextException("value is required for SVC Param " + k);
536
2
        }
537
19
        try {
538
19
          pdns::checked_stoi_into(port, portstring);
539
19
        } catch (const std::exception &e) {
540
4
          throw RecordTextException(e.what());
541
4
        }
542
19
      }
543
95
      val.insert(SvcParam(key, port));
544
95
      break;
545
130
    }
546
307
    case SvcParam::ech: {
547
307
      string value;
548
307
      if (generic) {
549
83
        xfrRFC1035CharString(value);
550
224
      } else {
551
224
        bool haveQuote = d_string.at(d_pos) == '"';
552
224
        if (haveQuote) {
553
8
          d_pos++;
554
8
        }
555
224
        xfrBlobNoSpaces(value);
556
224
        if (haveQuote) {
557
7
          if (d_string.at(d_pos) != '"') {
558
6
            throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
559
6
          }
560
1
          d_pos++;
561
1
        }
562
224
      }
563
301
      if (value.empty()) {
564
28
        throw RecordTextException("value is required for SVC Param " + k);
565
28
      }
566
273
      val.insert(SvcParam(key, value));
567
273
      break;
568
301
    }
569
194
    case SvcParam::tls_supported_groups: {
570
194
      string string_value;
571
194
      xfrRFC1035CharString(string_value);
572
194
      if (string_value.empty()) {
573
6
        throw RecordTextException("Value is required for SVC Param " + k);
574
6
      }
575
576
188
      vector<string> parts;
577
188
      stringtok(parts, string_value, ",");
578
579
188
      vector<uint16_t> values;
580
188
      values.reserve(parts.size());
581
119k
      for (const auto& part : parts) {
582
119k
        uint16_t int_part{0};
583
119k
        try {
584
119k
          pdns::checked_stoi_into(int_part, part);
585
119k
        } catch (const std::invalid_argument&) {
586
64
          throw RecordTextException("Value in invalid format for SVC Param " + k);
587
64
        }
588
119k
        values.emplace_back(int_part);
589
119k
      }
590
591
114
      val.insert(SvcParam(key, std::move(values)));
592
114
      break;
593
188
    }
594
61
    case SvcParam::dohpath: {
595
61
      string value;
596
61
      xfrRFC1035CharString(value);
597
61
      if (value.empty()) {
598
3
        throw RecordTextException("Value is required for SVC Param " + k);
599
3
      }
600
58
      val.insert(SvcParam(key, value));
601
58
      break;
602
61
    }
603
2.53k
    default: {
604
2.53k
      string value;
605
2.53k
      xfrRFC1035CharString(value);
606
2.53k
      if (!generic && value.empty()) {
607
        // for generic format, we do not know.
608
        // Known keys which forbid having a value need to implement a switch case, above.
609
0
        throw RecordTextException("value is required for SVC Param " + k);
610
0
      }
611
2.53k
      val.insert(SvcParam(key, value));
612
2.53k
      break;
613
2.53k
    }
614
5.31k
    }
615
5.31k
  }
616
2.65k
}
617
618
static inline uint8_t hextodec(uint8_t val)
619
188k
{
620
188k
  if(val >= '0' && val<='9')
621
168k
    return val-'0';
622
20.1k
  else if(val >= 'A' && val<='F')
623
2.75k
    return 10+(val-'A');
624
17.4k
  else if(val >= 'a' && val<='f')
625
17.3k
    return 10+(val-'a');
626
39
  else
627
39
    throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'");
628
188k
}
629
630
631
static void HEXDecode(std::string_view chunk, string& out)
632
428
{
633
428
  out.clear();
634
428
  if (chunk.length() == 1 && chunk[0] == '-') {
635
3
    return;
636
3
  }
637
425
  out.reserve(chunk.length() / 2);
638
425
  bool lowdigit{false};
639
425
  uint8_t val{0};
640
881k
  for (auto chr : chunk) {
641
881k
    if(isalnum(static_cast<unsigned char>(chr)) == 0) {
642
692k
      continue;
643
692k
    }
644
188k
    if (!lowdigit) {
645
94.3k
      val = 16*hextodec(chr);
646
94.3k
      lowdigit = true;
647
94.3k
    } else {
648
94.2k
      val += hextodec(chr);
649
94.2k
      out.append(1, (char) val);
650
94.2k
      lowdigit = false;
651
94.2k
      val = 0;
652
94.2k
    }
653
188k
  }
654
425
  if (lowdigit) {
655
50
    throw RecordTextException("Hexadecimal blob '" + std::string(chunk) + "' contains an odd number of hex digits");
656
50
  }
657
425
}
658
659
void RecordTextReader::xfrHexBlob(string& val, bool keepReading)
660
434
{
661
434
  skipSpaces();
662
434
  auto pos = d_pos;
663
1.02M
  while(d_pos < d_end && (keepReading || !dns_isspace(d_string[d_pos]))) {
664
1.02M
    d_pos++;
665
1.02M
  }
666
667
434
  HEXDecode(std::string_view(d_string).substr(pos, d_pos - pos), val);
668
434
}
669
670
void RecordTextReader::xfrBase32HexBlob(string& val)
671
229
{
672
229
  skipSpaces();
673
229
  auto pos = d_pos;
674
610k
  while(d_pos < d_end && !dns_isspace(d_string[d_pos])) {
675
610k
    d_pos++;
676
610k
  }
677
678
229
  val=fromBase32Hex(string(d_string.c_str()+pos, d_pos-pos));
679
229
}
680
681
682
void RecordTextWriter::xfrBase32HexBlob(const string& val)
683
424
{
684
424
  if(!d_string.empty())
685
424
    d_string.append(1,' ');
686
687
424
  d_string.append(toUpper(toBase32Hex(val)));
688
424
}
689
690
691
void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */)
692
1.12k
{
693
1.12k
  val.clear();
694
1.12k
  val.reserve(d_end - d_pos);
695
696
295k
  while (d_pos != d_end) {
697
295k
    if (!val.empty()) {
698
294k
      val.append(1, ' ');
699
294k
    }
700
701
295k
    skipSpaces();
702
295k
    char delimiter{'"'};
703
295k
    bool quoted = d_string[d_pos] == '"';
704
    // If the word is quoted, process up to the next quote; otherwise,
705
    // process up to the next whitespace (but output it in quotes).
706
295k
    val.append(1, '"');
707
295k
    if (quoted) {
708
196k
      ++d_pos;
709
196k
    }
710
98.6k
    else {
711
      // RFC1035: ``a contiguous set of characters without interior spaces''
712
98.6k
      delimiter = ' ';
713
98.6k
    }
714
2.16M
    while (d_pos != d_end && d_string[d_pos] != delimiter) {
715
1.95M
      if (d_string[d_pos] == '\\' && d_pos + 1 != d_end) {
716
21.1k
        val.append(1, d_string[d_pos++]); // copy escape slash
717
21.1k
        char chr = d_string[d_pos];
718
21.1k
        if (chr >= '0' && chr <= '9') {
719
2.07k
          bool valid{false};
720
          // Must be a three-digit character escape sequence
721
2.07k
          if (d_end - d_pos >= 3) {
722
2.07k
            char chr2 = d_string[d_pos + 1];
723
2.07k
            char chr3 = d_string[d_pos + 2];
724
2.07k
            if (chr2 >= '0' && chr2 <= '9' && chr3 >= '0' && chr3 <= '9') {
725
2.04k
              valid = 100 * (chr - '0') + 10 * (chr2 - '0') + chr3 - '0' < 256;
726
2.04k
            }
727
2.07k
          }
728
2.07k
          if (!valid) {
729
37
            throw RecordTextException("Data field in DNS contains an invalid escape at position "+std::to_string(d_pos)+" of '"+d_string+"'");
730
37
          }
731
2.07k
        }
732
        // Not advancing d_pos, we'll append the next 1 or 3 characters as
733
        // part of the regular case.
734
21.1k
      }
735
1.95M
      if (!quoted && d_string[d_pos] == '"') {
736
        // Bind allows a non-quoted text to be immediately followed by a
737
        // quoted text, without any whitespace in between, so handle this
738
        // as a delimiter.
739
90.8k
        break;
740
90.8k
      }
741
1.86M
      val.append(1, d_string[d_pos]);
742
1.86M
      ++d_pos;
743
1.86M
    }
744
295k
    val.append(1,'"');
745
295k
    if (quoted) {
746
      // If we reached the end in a quoted section, the closing quote is missing.
747
196k
      if (d_pos == d_end) {
748
56
        throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string+"'");
749
56
      }
750
      // Skip closing quote
751
196k
      ++d_pos;
752
196k
    }
753
295k
    if (!multi) {
754
367
      break;
755
367
    }
756
295k
  }
757
1.12k
}
758
759
void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
760
121
{
761
121
  val.clear();
762
121
  val.reserve(d_end - d_pos);
763
764
121
  if(!val.empty())
765
0
    val.append(1, ' ');
766
767
121
  skipSpaces();
768
121
  val.append(1, d_string[d_pos]);
769
228k
  while(++d_pos < d_end && d_string[d_pos] != ' '){
770
228k
    val.append(1, d_string[d_pos]);
771
228k
  }
772
121
}
773
774
void RecordTextReader::xfrType(uint16_t& val)
775
5.21k
{
776
5.21k
  skipSpaces();
777
5.21k
  auto pos = d_pos;
778
656k
  while(d_pos < d_end && !dns_isspace(d_string[d_pos])) {
779
650k
    d_pos++;
780
650k
  }
781
782
5.21k
  string tmp;
783
5.21k
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
784
785
5.21k
  val=DNSRecordContent::TypeToNumber(tmp);
786
5.21k
}
787
788
789
void RecordTextReader::skipSpaces()
790
319k
{
791
319k
  const char* strptr = d_string.c_str();
792
341k
  while(d_pos < d_end && dns_isspace(strptr[d_pos]))
793
22.1k
    d_pos++;
794
319k
  if(d_pos == d_end)
795
380
    throw RecordTextException("missing field at the end of record content '"+d_string+"'");
796
319k
}
797
798
799
8.30k
RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str)
800
8.30k
{
801
8.30k
  d_string.clear();
802
8.30k
  d_nodot=noDot;
803
8.30k
}
804
805
void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
806
28
{
807
28
  if(!d_string.empty()) {
808
28
    d_string.append(1,' ');
809
28
  }
810
811
28
  size_t ctr = 0;
812
28
  char tmp[5];
813
224
  for (auto const &c : val.content) {
814
224
    snprintf(tmp, sizeof(tmp), "%02X", c);
815
224
    d_string+=tmp;
816
224
    ctr++;
817
224
    if (ctr % 2 == 0 && ctr != 8) {
818
84
      d_string+=':';
819
84
    }
820
224
  }
821
28
}
822
823
void RecordTextWriter::xfr48BitInt(const uint64_t& val)
824
0
{
825
0
  if(!d_string.empty())
826
0
    d_string.append(1,' ');
827
0
  d_string+=std::to_string(val);
828
0
}
829
830
831
void RecordTextWriter::xfr32BitInt(const uint32_t& val)
832
7.82k
{
833
7.82k
  if(!d_string.empty())
834
2.79k
    d_string.append(1,' ');
835
7.82k
  d_string+=std::to_string(val);
836
7.82k
}
837
838
void RecordTextWriter::xfrType(const uint16_t& val)
839
75
{
840
75
  if(!d_string.empty())
841
0
    d_string.append(1,' ');
842
75
  d_string+=DNSRecordContent::NumberToType(val);
843
75
}
844
845
// this function is on the fast path for the pdns_recursor
846
void RecordTextWriter::xfrIP(const uint32_t& val)
847
41
{
848
41
  if(!d_string.empty())
849
10
    d_string.append(1,' ');
850
851
41
  char tmp[17];
852
41
  uint32_t ip=val;
853
41
  uint8_t vals[4];
854
855
41
  memcpy(&vals[0], &ip, sizeof(ip));
856
857
41
  char *pos=tmp;
858
859
205
  for(int n=0; n < 4; ++n) {
860
164
    if(vals[n]<10) {
861
74
      *(pos++)=vals[n]+'0';
862
90
    } else if(vals[n] < 100) {
863
51
      *(pos++)=(vals[n]/10) +'0';
864
51
      *(pos++)=(vals[n]%10) +'0';
865
51
    } else {
866
39
      *(pos++)=(vals[n]/100) +'0';
867
39
      vals[n]%=100;
868
39
      *(pos++)=(vals[n]/10) +'0';
869
39
      *(pos++)=(vals[n]%10) +'0';
870
39
    }
871
164
    if(n!=3)
872
123
      *(pos++)='.';
873
164
  }
874
41
  *pos=0;
875
41
  d_string.append(tmp, pos);
876
41
}
877
878
void RecordTextWriter::xfrIP6(const std::string& val)
879
33
{
880
33
  char tmpbuf[16];
881
33
  char addrbuf[40];
882
883
33
  if(!d_string.empty())
884
7
   d_string.append(1,' ');
885
886
33
  val.copy(tmpbuf,16);
887
888
33
  if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr)
889
0
    throw RecordTextException("Unable to convert to ipv6 address");
890
891
33
  d_string += std::string(addrbuf);
892
33
}
893
894
void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
895
0
{
896
0
  string ip = val.toString();
897
898
0
  if(!d_string.empty())
899
0
    d_string.append(1,' ');
900
901
0
  d_string += ip;
902
0
}
903
904
void RecordTextWriter::xfrCAPort(ComboAddress &val)
905
0
{
906
0
  xfr16BitInt(val.sin4.sin_port);
907
0
}
908
909
void RecordTextWriter::xfrTime(const uint32_t& val)
910
150
{
911
150
  if(!d_string.empty())
912
150
    d_string.append(1,' ');
913
914
150
  struct tm tm;
915
150
  time_t time=val; // Y2038 bug!
916
150
  gmtime_r(&time, &tm);
917
918
150
  static const boost::format fmt("%04d%02d%02d" "%02d%02d%02d");
919
150
  d_string += boost::str(boost::format(fmt) % (tm.tm_year+1900) % (tm.tm_mon+1) % tm.tm_mday % tm.tm_hour % tm.tm_min % tm.tm_sec);
920
150
}
921
922
923
void RecordTextWriter::xfr16BitInt(const uint16_t& val)
924
4.70k
{
925
4.70k
  xfr32BitInt(val);
926
4.70k
}
927
928
void RecordTextWriter::xfr8BitInt(const uint8_t& val)
929
2.69k
{
930
2.69k
  xfr32BitInt(val);
931
2.69k
}
932
933
// should not mess with the escapes
934
void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */)
935
5.41k
{
936
5.41k
  if(!d_string.empty())
937
3.66k
    d_string.append(1,' ');
938
939
5.41k
  if(d_nodot) {
940
0
    d_string+=val.toStringRootDot();
941
0
  }
942
5.41k
  else
943
5.41k
  {
944
5.41k
    d_string+=val.toString();
945
5.41k
  }
946
5.41k
}
947
948
void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size)
949
446
{
950
446
  xfrBlob(val, size);
951
446
}
952
953
void RecordTextWriter::xfrBlob(const string& val, int)
954
1.27k
{
955
1.27k
  if(!d_string.empty())
956
270
    d_string.append(1,' ');
957
958
1.27k
  d_string+=Base64Encode(val);
959
1.27k
}
960
961
void RecordTextWriter::xfrHexBlob(const string& val, bool)
962
917
{
963
917
  if(!d_string.empty())
964
917
    d_string.append(1,' ');
965
966
917
  if(val.empty()) {
967
429
    d_string.append(1,'-');
968
429
    return;
969
429
  }
970
971
488
  string::size_type limit=val.size();
972
488
  char tmp[5];
973
671k
  for(string::size_type n = 0; n < limit; ++n) {
974
670k
    snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]);
975
670k
    d_string+=tmp;
976
670k
  }
977
488
}
978
979
857
void RecordTextWriter::xfrSVCBValueList(const vector<string> &val) {
980
857
  bool shouldQuote{false};
981
857
  vector<string> escaped;
982
857
  escaped.reserve(val.size());
983
534k
  for (auto const &v : val) {
984
534k
    if (v.find_first_of(' ') != string::npos) {
985
698
      shouldQuote = true;
986
698
    }
987
534k
    string tmp = txtEscape(v);
988
534k
    string unescaped;
989
534k
    unescaped.reserve(tmp.size() + 4);
990
10.8M
    for (auto const &ch : tmp) {
991
10.8M
      if (ch == '\\') {
992
2.17M
        unescaped += R"F(\\)F";
993
2.17M
        continue;
994
2.17M
      }
995
8.71M
      if (ch == ',') {
996
18.8k
        unescaped += R"F(\\,)F";
997
18.8k
        continue;
998
18.8k
      }
999
8.70M
      unescaped += ch;
1000
8.70M
    }
1001
534k
    escaped.push_back(std::move(unescaped));
1002
534k
  }
1003
857
  if (shouldQuote) {
1004
70
    d_string.append(1, '"');
1005
70
  }
1006
857
  d_string.append(boost::join(escaped, ","));
1007
857
  if (shouldQuote) {
1008
70
    d_string.append(1, '"');
1009
70
  }
1010
857
}
1011
1012
3.08k
void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
1013
7.36k
  for (auto const &param : val) {
1014
7.36k
    if (!d_string.empty())
1015
7.36k
      d_string.append(1, ' ');
1016
1017
7.36k
    d_string.append(SvcParam::keyToString(param.getKey()));
1018
7.36k
    if (param.getKey() != SvcParam::no_default_alpn && param.getKey() != SvcParam::ohttp) {
1019
7.05k
      d_string.append(1, '=');
1020
7.05k
    }
1021
1022
7.36k
    switch (param.getKey())
1023
7.36k
    {
1024
188
    case SvcParam::no_default_alpn:
1025
188
      break;
1026
384
    case SvcParam::ipv4hint: /* fall-through */
1027
772
    case SvcParam::ipv6hint:
1028
      // TODO use xfrCA and put commas in between?
1029
772
      if (param.getAutoHint()) {
1030
15
        d_string.append("auto");
1031
15
        break;
1032
15
      }
1033
757
      d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
1034
757
      break;
1035
857
    case SvcParam::alpn:
1036
857
      xfrSVCBValueList(param.getALPN());
1037
857
      break;
1038
596
    case SvcParam::mandatory:
1039
596
    {
1040
596
      bool doComma = false;
1041
24.4k
      for (auto const &k: param.getMandatory()) {
1042
24.4k
        if (doComma)
1043
23.8k
          d_string.append(1, ',');
1044
24.4k
        d_string.append(SvcParam::keyToString(k));
1045
24.4k
        doComma = true;
1046
24.4k
      }
1047
596
      break;
1048
772
    }
1049
174
    case SvcParam::port: {
1050
174
      auto str = d_string;
1051
174
      d_string.clear();
1052
174
      xfr16BitInt(param.getPort());
1053
174
      d_string = str + d_string;
1054
174
      break;
1055
772
    }
1056
446
    case SvcParam::ech: {
1057
446
      auto str = d_string;
1058
446
      d_string.clear();
1059
446
      xfrBlobNoSpaces(param.getECH());
1060
446
      d_string = str + '"' + d_string + '"';
1061
446
      break;
1062
772
    }
1063
128
    case SvcParam::ohttp:
1064
      // no value
1065
128
      break;
1066
203
    case SvcParam::tls_supported_groups: {
1067
203
      auto str = d_string;
1068
203
      d_string.clear();
1069
203
      bool first = true;
1070
193k
      for (auto const &group: param.getTLSSupportedGroups()) {
1071
193k
        if (!first) {
1072
193k
          str += ',';
1073
193k
        }
1074
193k
        str += std::to_string(group);
1075
193k
        first = false;
1076
193k
      }
1077
203
      d_string = std::move(str);
1078
203
      break;
1079
772
    }
1080
106
    case SvcParam::dohpath:
1081
4.00k
    default:
1082
4.00k
      auto str = d_string;
1083
4.00k
      d_string.clear();
1084
4.00k
      xfrText(param.getValue(), false, false);
1085
4.00k
      d_string = str + '"' + txtEscape(d_string) + '"';
1086
4.00k
      break;
1087
7.36k
    }
1088
7.36k
  }
1089
3.08k
}
1090
1091
void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
1092
5.81k
{
1093
5.81k
  if(!d_string.empty())
1094
801
    d_string.append(1,' ');
1095
1096
5.81k
  if (val.empty()) {
1097
3.18k
    d_string.append(2, '"');
1098
3.18k
  }
1099
2.62k
  else {
1100
2.62k
    d_string.append(val);
1101
2.62k
  }
1102
5.81k
}
1103
1104
void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
1105
269
{
1106
269
  if(!d_string.empty())
1107
269
    d_string.append(1,' ');
1108
269
  d_string.append(val);
1109
269
}
1110
1111
#ifdef TESTING
1112
1113
int main(int argc, char**argv)
1114
try
1115
{
1116
  RecordTextReader rtr(argv[1], argv[2]);
1117
1118
  unsigned int order, pref;
1119
  string flags, services, regexp, replacement;
1120
  string mx;
1121
1122
  rtr.xfrInt(order);
1123
  rtr.xfrInt(pref);
1124
  rtr.xfrText(flags);
1125
  rtr.xfrText(services);
1126
  rtr.xfrText(regexp);
1127
  rtr.xfrName(replacement);
1128
1129
  cout<<"order: "<<order<<", pref: "<<pref<<"\n";
1130
  cout<<"flags: \""<<flags<<"\", services: \""<<services<<"\", regexp: \""<<regexp<<"\", replacement: "<<replacement<<"\n";
1131
1132
  string out;
1133
  RecordTextWriter rtw(out);
1134
1135
  rtw.xfrInt(order);
1136
  rtw.xfrInt(pref);
1137
  rtw.xfrText(flags);
1138
  rtw.xfrText(services);
1139
  rtw.xfrText(regexp);
1140
  rtw.xfrName(replacement);
1141
1142
  cout<<"Regenerated: '"<<out<<"'\n";
1143
1144
}
1145
catch(std::exception& e)
1146
{
1147
  cerr<<"Fatal: "<<e.what()<<endl;
1148
}
1149
1150
#endif