Coverage Report

Created: 2026-06-07 07:03

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