]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/rcpgenerator.cc
rec: allow exception to proxy protocal usage for specific listen addresses
[thirdparty/pdns.git] / pdns / rcpgenerator.cc
CommitLineData
4192ca66 1/*
6edbf68a
PL
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 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
4192ca66 25#include "rcpgenerator.hh"
20133c59 26#include "dnsparser.hh"
cbf0e7f3 27#include "misc.hh"
3897b9e1 28#include "utility.hh"
12b33ac2 29#include <boost/algorithm/string.hpp>
ff7ac440 30#include <boost/algorithm/string/classification.hpp>
a064bd5a 31#include <boost/algorithm/string/replace.hpp>
a48e03da 32#include <boost/format.hpp>
ff7ac440 33
4192ca66 34#include <iostream>
1c4d88c5 35#include "base32.hh"
8c1c9170 36#include "base64.hh"
d2d6cf61 37#include "namespaces.hh"
4192ca66 38
1290c1d2 39RecordTextReader::RecordTextReader(string str, DNSName zone) : d_string(std::move(str)), d_zone(std::move(zone)), d_pos(0)
4192ca66 40{
ff7ac440 41 /* remove whitespace */
13653a1a 42 if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) ))
5ead94ed 43 boost::trim_if(d_string, dns_isspace);
ff7ac440 44 d_end = d_string.size();
4192ca66
BH
45}
46
341930bb
BH
47void RecordTextReader::xfr48BitInt(uint64_t &val)
48{
49 xfr64BitInt(val);
335da0ba
AT
50 if (val > 281474976710655LL)
51 throw RecordTextException("Overflow reading 48 bit integer from record content"); // fixme improve
341930bb
BH
52}
53
786ed0ff
PL
54void RecordTextReader::xfrNodeOrLocatorID(NodeOrLocatorID& val) {
55 skipSpaces();
56 size_t len;
57 for(len=0;
58 d_pos+len < d_string.length() && (isxdigit(d_string.at(d_pos+len)) || d_string.at(d_pos+len) == ':');
59 len++) ; // find length of ID
60
61 // Parse as v6, and then strip the final 64 zero bytes
62 struct in6_addr tmpbuf;
63 string to_parse = d_string.substr(d_pos, len) + ":0:0:0:0";
64
65 if (inet_pton(AF_INET6, to_parse.c_str(), &tmpbuf) != 1) {
66 throw RecordTextException("while parsing colon-delimited 64-bit field: '" + d_string.substr(d_pos, len) + "' is invalid");
67 }
68
d97a781e 69 std::memcpy(&val.content, tmpbuf.s6_addr, sizeof(val.content));
786ed0ff
PL
70 d_pos += len;
71}
72
341930bb
BH
73void RecordTextReader::xfr64BitInt(uint64_t &val)
74{
75 skipSpaces();
76
77 if(!isdigit(d_string.at(d_pos)))
335da0ba 78 throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
341930bb 79
335da0ba
AT
80 size_t pos;
81 val=std::stoull(d_string.substr(d_pos), &pos);
1e05b07c 82
335da0ba 83 d_pos += pos;
341930bb
BH
84}
85
86
cbf0e7f3 87void RecordTextReader::xfr32BitInt(uint32_t &val)
4192ca66
BH
88{
89 skipSpaces();
90
91 if(!isdigit(d_string.at(d_pos)))
335da0ba 92 throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
4192ca66 93
335da0ba 94 size_t pos;
a0383aad
FM
95 val = pdns::checked_stoi<uint32_t>(d_string.c_str() + d_pos, &pos);
96
335da0ba 97 d_pos += pos;
4192ca66
BH
98}
99
8bf26468
BH
100void RecordTextReader::xfrTime(uint32_t &val)
101{
102 struct tm tm;
103 memset(&tm, 0, sizeof(tm));
1e05b07c 104
4a51ff72 105 uint64_t itmp;
7c06b0d7 106 xfr64BitInt(itmp);
4a51ff72 107
ed07d647
SB
108 if (itmp <= (uint32_t)~0) {
109 // formatted as seconds since epoch, not as YYYYMMDDHHmmSS:
110 val = (uint32_t) itmp;
111 return;
112 }
113
4a51ff72
PD
114 ostringstream tmp;
115
116 tmp<<itmp;
8bf26468 117
1817fef6
RG
118 if (sscanf(tmp.str().c_str(), "%04d%02d%02d" "%02d%02d%02d",
119 &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
120 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
121 throw RecordTextException("unable to parse '"+std::to_string(itmp)+"' into a valid time at position "+std::to_string(d_pos)+" in '"+d_string+"'");
122 }
8bf26468
BH
123
124 tm.tm_year-=1900;
12b33ac2 125 tm.tm_mon-=1;
b59b334d 126 // coverity[store_truncates_time_t]
1e05b07c 127 val=(uint32_t)Utility::timegm(&tm);
8bf26468
BH
128}
129
cbf0e7f3
BH
130void RecordTextReader::xfrIP(uint32_t &val)
131{
132 skipSpaces();
133
134 if(!isdigit(d_string.at(d_pos)))
335da0ba 135 throw RecordTextException("while parsing IP address, expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
1149fa0b
BH
136
137 uint32_t octet=0;
138 val=0;
139 char count=0;
267951e6
SB
140 bool last_was_digit = false;
141
1149fa0b
BH
142 for(;;) {
143 if(d_string.at(d_pos)=='.') {
267951e6
SB
144 if (!last_was_digit)
145 throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
146 last_was_digit = false;
1149fa0b
BH
147 val<<=8;
148 val+=octet;
149 octet=0;
150 count++;
151 if(count > 3)
267951e6 152 throw RecordTextException(string("unable to parse IP address, too many dots"));
1149fa0b
BH
153 }
154 else if(isdigit(d_string.at(d_pos))) {
267951e6 155 last_was_digit = true;
1149fa0b
BH
156 octet*=10;
157 octet+=d_string.at(d_pos) - '0';
158 if(octet > 255)
4957a608 159 throw RecordTextException("unable to parse IP address");
1149fa0b 160 }
1e05b07c 161 else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',')
1149fa0b 162 break;
87c837a3
BH
163 else {
164 throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
165 }
1149fa0b
BH
166 d_pos++;
167 if(d_pos == d_string.length())
168 break;
169 }
267951e6
SB
170 if (count != 3)
171 throw RecordTextException(string("unable to parse IP address, not enough dots"));
172 if (!last_was_digit)
173 throw RecordTextException(string("unable to parse IP address, trailing dot"));
174 val<<=8;
175 val+=octet;
1149fa0b 176 val=ntohl(val);
cbf0e7f3
BH
177}
178
179
b9b28916
AT
180void RecordTextReader::xfrIP6(std::string &val)
181{
b9b28916
AT
182 struct in6_addr tmpbuf;
183
184 skipSpaces();
1e05b07c 185
b9b28916 186 size_t len;
cf90f8e1 187 // lookup end of value - think of ::ffff encoding too, has dots in it!
1e05b07c 188 for(len=0;
cf90f8e1 189 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)=='.');
8d388c48 190 len++);
b9b28916 191
8d388c48 192 if(!len)
335da0ba 193 throw RecordTextException("while parsing IPv6 address, expected xdigits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
b9b28916 194
8d388c48 195 // end of value is here, try parse as IPv6
196 string address=d_string.substr(d_pos, len);
1e05b07c 197
8d388c48 198 if (inet_pton(AF_INET6, address.c_str(), &tmpbuf) != 1) {
199 throw RecordTextException("while parsing IPv6 address: '" + address + "' is invalid");
b9b28916
AT
200 }
201
202 val = std::string((char*)tmpbuf.s6_addr, 16);
203
204 d_pos += len;
205}
206
f4352636
PD
207void RecordTextReader::xfrCAWithoutPort(uint8_t version, ComboAddress &val)
208{
209 if (version == 4) {
210 uint32_t ip;
211 xfrIP(ip);
212 val = makeComboAddressFromRaw(4, string((const char*) &ip, 4));
213 }
214 else if (version == 6) {
215 string ip;
216 xfrIP6(ip);
217 val = makeComboAddressFromRaw(6, ip);
218 }
219 else throw RecordTextException("invalid address family");
220}
221
222void RecordTextReader::xfrCAPort(ComboAddress &val)
223{
224 uint16_t port;
225 xfr16BitInt(port);
226 val.sin4.sin_port = port;
227}
228
20133c59
BH
229bool RecordTextReader::eof()
230{
231 return d_pos==d_end;
232}
233
cbf0e7f3
BH
234void RecordTextReader::xfr16BitInt(uint16_t &val)
235{
236 uint32_t tmp;
237 xfr32BitInt(tmp);
238 val=tmp;
239 if(val!=tmp)
240 throw RecordTextException("Overflow reading 16 bit integer from record content"); // fixme improve
241}
242
8c1c9170
BH
243void RecordTextReader::xfr8BitInt(uint8_t &val)
244{
245 uint32_t tmp;
246 xfr32BitInt(tmp);
247 val=tmp;
248 if(val!=tmp)
249 throw RecordTextException("Overflow reading 8 bit integer from record content"); // fixme improve
250}
251
1e05b07c 252// this code should leave all the escapes around
f21fc0aa 253void RecordTextReader::xfrName(DNSName& val, bool, bool)
4192ca66
BH
254{
255 skipSpaces();
3716f081 256 DNSName sval;
29a14b24 257
83b77f90 258 string::size_type begin_pos = d_pos;
3b45a434 259 while (d_pos < d_end) {
91471f5c 260 if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
29a14b24 261 break;
3b45a434 262 }
1e05b07c 263
4192ca66 264 d_pos++;
29a14b24 265 }
91471f5c
RG
266
267 {
268 std::string_view view(d_string);
269 sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
270 }
83b77f90 271
3b45a434
RG
272 if (sval.empty()) {
273 sval = d_zone;
274 }
275 else if (!d_zone.empty()) {
276 sval += d_zone;
277 }
278 val = std::move(sval);
4192ca66
BH
279}
280
2fe9d6f7 281static bool isbase64(char c, bool acceptspace)
12b33ac2
BH
282{
283 if(dns_isspace(c))
2fe9d6f7 284 return acceptspace;
12b33ac2
BH
285 if(c >= '0' && c <= '9')
286 return true;
1e05b07c 287 if(c >= 'a' && c <= 'z')
12b33ac2 288 return true;
1e05b07c 289 if(c >= 'A' && c <= 'Z')
12b33ac2
BH
290 return true;
291 if(c=='+' || c=='/' || c=='=')
292 return true;
293 return false;
294}
295
2fe9d6f7
AT
296void RecordTextReader::xfrBlobNoSpaces(string& val, int len) {
297 skipSpaces();
298 int pos=(int)d_pos;
299 const char* strptr=d_string.c_str();
1e05b07c 300 while(d_pos < d_end && isbase64(strptr[d_pos], false))
2fe9d6f7
AT
301 d_pos++;
302
303 string tmp;
304 tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
305 boost::erase_all(tmp," ");
306 val.clear();
307 B64Decode(tmp, val);
1e05b07c 308
2fe9d6f7 309 if (len>-1 && val.size() != static_cast<size_t>(len))
335da0ba 310 throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len));
2fe9d6f7
AT
311}
312
06ffdc52 313void RecordTextReader::xfrBlob(string& val, int)
8c1c9170
BH
314{
315 skipSpaces();
705f31ae 316 int pos=(int)d_pos;
d39704c0 317 const char* strptr=d_string.c_str();
2fe9d6f7 318 while(d_pos < d_end && isbase64(strptr[d_pos], true))
8c1c9170 319 d_pos++;
1e05b07c 320
20133c59
BH
321 string tmp;
322 tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
12b33ac2 323 boost::erase_all(tmp," ");
20133c59
BH
324 val.clear();
325 B64Decode(tmp, val);
8c1c9170
BH
326}
327
b1a048a9
PL
328void RecordTextReader::xfrRFC1035CharString(string &val) {
329 auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val);
330 d_pos += ctr;
331}
332
d1082642
PL
333void RecordTextReader::xfrSVCBValueList(vector<string> &val) {
334 auto ctr = parseSVCBValueList(d_string.substr(d_pos, d_end - d_pos), val);
335 d_pos += ctr;
336}
337
373914dc
PL
338void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
339{
340 while (d_pos != d_end) {
341 skipSpaces();
342 if (d_pos == d_end)
343 return;
344
345 // Find the SvcParamKey
346 size_t pos = d_pos;
347 while (d_pos != d_end) {
348 if (d_string.at(d_pos) == '=' || d_string.at(d_pos) == ' ') {
349 break;
350 }
351 d_pos++;
352 }
353
354 // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
355 string k = d_string.substr(pos, d_pos - pos);
356 SvcParam::SvcParamKey key;
76a98652 357 bool generic;
373914dc 358 try {
76a98652 359 key = SvcParam::keyFromString(k, generic);
373914dc
PL
360 } catch (const std::invalid_argument &e) {
361 throw RecordTextException(e.what());
362 }
363
364 if (key != SvcParam::no_default_alpn) {
365 if (d_pos == d_end || d_string.at(d_pos) != '=') {
366 throw RecordTextException("expected '=' after " + k);
367 }
368 d_pos++; // Now on the first character after '='
369 if (d_pos == d_end || d_string.at(d_pos) == ' ') {
370 throw RecordTextException("expected value after " + k + "=");
371 }
372 }
373
374 switch (key) {
375 case SvcParam::no_default_alpn:
376 if (d_pos != d_end && d_string.at(d_pos) == '=') {
377 throw RecordTextException(k + " key can not have values");
378 }
379 val.insert(SvcParam(key));
380 break;
381 case SvcParam::ipv4hint: /* fall-through */
382 case SvcParam::ipv6hint: {
383 vector<ComboAddress> hints;
ed34c66a 384 bool doAuto{false};
76a98652
PL
385 if (generic) {
386 string value;
387 xfrRFC1035CharString(value);
388 size_t len = key == SvcParam::ipv4hint ? 4 : 16;
389 if (value.size() % len != 0) {
390 throw RecordTextException(k + " in generic format has wrong number of bytes");
391 }
392 for (size_t i=0; i<value.size(); i += len) {
393 auto hint = makeComboAddressFromRaw(static_cast<uint8_t>(key), &value.at(i), len);
394 hints.push_back(hint);
395 }
396 } else {
397 vector<string> value;
398 xfrSVCBValueList(value);
d1082642 399 for (auto const &v: value) {
ed34c66a
PL
400 if (v == "auto") {
401 doAuto = true;
402 hints.clear();
403 break;
404 }
d1082642
PL
405 hints.push_back(ComboAddress(v));
406 }
76a98652
PL
407 }
408 try {
ed34c66a
PL
409 auto p = SvcParam(key, std::move(hints));
410 p.setAutoHint(doAuto);
411 val.insert(p);
373914dc
PL
412 }
413 catch (const std::invalid_argument& e) {
414 throw RecordTextException(e.what());
415 }
416 break;
417 }
418 case SvcParam::alpn: {
d1082642 419 vector<string> value;
76a98652
PL
420 if (generic) {
421 string v;
422 xfrRFC1035CharString(v);
423 size_t spos{0}, len;
424 while (spos < v.length()) {
425 len = v.at(spos);
426 spos += 1;
427 if (len > v.length() - spos) {
428 throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
429 }
430 value.push_back(v.substr(spos, len));
431 spos += len;
432 }
433 } else {
434 xfrSVCBValueList(value);
435 }
d1082642 436 val.insert(SvcParam(key, std::move(value)));
373914dc
PL
437 break;
438 }
439 case SvcParam::mandatory: {
76a98652
PL
440 if (generic) {
441 string v;
442 xfrRFC1035CharString(v);
443 if (v.length() % 2 != 0) {
444 throw RecordTextException("Wrong number of bytes in SVC Param " + k);
445 }
446 std::set<SvcParam::SvcParamKey> keys;
447 for (size_t i=0; i < v.length(); i += 2) {
448 uint16_t mand = (v.at(i) << 8);
449 mand += v.at(i+1);
450 keys.insert(SvcParam::SvcParamKey(mand));
451 }
452 val.insert(SvcParam(key, std::move(keys)));
453 break;
454 }
373914dc 455 vector<string> parts;
d1082642 456 xfrSVCBValueList(parts);
373914dc 457 set<string> values(parts.begin(), parts.end());
96fdac2a 458 val.insert(SvcParam(key, std::move(values)));
373914dc
PL
459 break;
460 }
461 case SvcParam::port: {
462 uint16_t port;
76a98652
PL
463 if (generic) {
464 string v;
465 xfrRFC1035CharString(v);
466 if (v.length() != 2) {
467 throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v.length()));
468 }
469 port = (v.at(0) << 8);
470 port += v.at(1);
471 } else {
472 xfr16BitInt(port);
473 }
373914dc
PL
474 val.insert(SvcParam(key, port));
475 break;
476 }
4f254e34 477 case SvcParam::ech: {
373914dc 478 string value;
76a98652
PL
479 if (generic) {
480 xfrRFC1035CharString(value);
481 } else {
482 bool haveQuote = d_string.at(d_pos) == '"';
483 if (haveQuote) {
484 d_pos++;
485 }
486 xfrBlobNoSpaces(value);
487 if (haveQuote) {
488 if (d_string.at(d_pos) != '"') {
489 throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
490 }
491 d_pos++;
d1082642 492 }
373914dc
PL
493 }
494 val.insert(SvcParam(key, value));
495 break;
496 }
497 default: {
498 string value;
b1a048a9 499 xfrRFC1035CharString(value);
373914dc
PL
500 val.insert(SvcParam(key, value));
501 break;
502 }
503 }
504 }
505}
59a0f653
BH
506
507static inline uint8_t hextodec(uint8_t val)
508{
509 if(val >= '0' && val<='9')
510 return val-'0';
511 else if(val >= 'A' && val<='F')
512 return 10+(val-'A');
513 else if(val >= 'a' && val<='f')
514 return 10+(val-'a');
515 else
335da0ba 516 throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'");
59a0f653
BH
517}
518
519
050e6877 520static void HEXDecode(const char* begin, const char* end, string& out)
59a0f653 521{
3c873e66 522 if(end - begin == 1 && *begin=='-') {
4e75f84a 523 out.clear();
3c873e66
BH
524 return;
525 }
4e75f84a
BH
526 out.clear();
527 out.reserve((end-begin)/2);
528 uint8_t mode=0, val=0;
529 for(; begin != end; ++begin) {
530 if(!isalnum(*begin))
531 continue;
532 if(mode==0) {
533 val = 16*hextodec(*begin);
534 mode=1;
535 } else {
1e05b07c 536 val += hextodec(*begin);
4e75f84a
BH
537 out.append(1, (char) val);
538 mode = 0;
539 val = 0;
540 }
59a0f653 541 }
4e75f84a
BH
542 if(mode)
543 out.append(1, (char) val);
544
59a0f653
BH
545}
546
e4090157 547void RecordTextReader::xfrHexBlob(string& val, bool keepReading)
59a0f653
BH
548{
549 skipSpaces();
705f31ae 550 int pos=(int)d_pos;
e4090157 551 while(d_pos < d_end && (keepReading || !dns_isspace(d_string[d_pos])))
59a0f653
BH
552 d_pos++;
553
554 HEXDecode(d_string.c_str()+pos, d_string.c_str() + d_pos, val);
555}
556
8b606f6e
BH
557void RecordTextReader::xfrBase32HexBlob(string& val)
558{
559 skipSpaces();
560 int pos=(int)d_pos;
561 while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
562 d_pos++;
563
564 val=fromBase32Hex(string(d_string.c_str()+pos, d_pos-pos));
565}
566
567
1c4d88c5
BH
568void RecordTextWriter::xfrBase32HexBlob(const string& val)
569{
570 if(!d_string.empty())
571 d_string.append(1,' ');
572
1bad4190 573 d_string.append(toUpper(toBase32Hex(val)));
1c4d88c5
BH
574}
575
576
d73de874 577void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */)
4192ca66 578{
4192ca66
BH
579 val.clear();
580 val.reserve(d_end - d_pos);
ef6a78d5
BH
581
582 while(d_pos != d_end) {
583 if(!val.empty())
584 val.append(1, ' ');
585
586 skipSpaces();
2220e01f
BH
587 if(d_string[d_pos]!='"') { // special case 'plenus' - without quotes
588 string::size_type pos = d_pos;
589 while(pos != d_end && isalnum(d_string[pos]))
590 pos++;
591 if(pos == d_end) {
592 val.append(1, '"');
593 val.append(d_string.c_str() + d_pos, d_end - d_pos);
594 val.append(1, '"');
595 d_pos = d_end;
596 break;
597 }
335da0ba 598 throw RecordTextException("Data field in DNS should start with quote (\") at position "+std::to_string(d_pos)+" of '"+d_string+"'");
2220e01f 599 }
ef6a78d5
BH
600 val.append(1, '"');
601 while(++d_pos < d_end && d_string[d_pos]!='"') {
602 if(d_string[d_pos]=='\\' && d_pos+1!=d_end) {
4957a608 603 val.append(1, d_string[d_pos++]);
ef6a78d5
BH
604 }
605 val.append(1, d_string[d_pos]);
4192ca66 606 }
ef6a78d5
BH
607 val.append(1,'"');
608 if(d_pos == d_end)
609 throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string+"'");
610 d_pos++;
611 if(!multi)
612 break;
4192ca66 613 }
4192ca66
BH
614}
615
d73de874 616void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
948a927f
PL
617{
618 val.clear();
619 val.reserve(d_end - d_pos);
620
621 if(!val.empty())
622 val.append(1, ' ');
623
624 skipSpaces();
625 val.append(1, d_string[d_pos]);
626 while(++d_pos < d_end && d_string[d_pos] != ' '){
627 val.append(1, d_string[d_pos]);
628 }
629}
630
20133c59
BH
631void RecordTextReader::xfrType(uint16_t& val)
632{
633 skipSpaces();
705f31ae 634 int pos=(int)d_pos;
ec486449 635 while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
20133c59
BH
636 d_pos++;
637
638 string tmp;
639 tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
640
641 val=DNSRecordContent::TypeToNumber(tmp);
642}
4192ca66 643
8c1c9170 644
4192ca66
BH
645void RecordTextReader::skipSpaces()
646{
d39704c0
BH
647 const char* strptr = d_string.c_str();
648 while(d_pos < d_end && dns_isspace(strptr[d_pos]))
4192ca66 649 d_pos++;
4192ca66
BH
650 if(d_pos == d_end)
651 throw RecordTextException("missing field at the end of record content '"+d_string+"'");
652}
653
654
f21fc0aa 655RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str)
4192ca66
BH
656{
657 d_string.clear();
f21fc0aa 658 d_nodot=noDot;
4192ca66
BH
659}
660
786ed0ff
PL
661void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
662{
663 if(!d_string.empty()) {
664 d_string.append(1,' ');
665 }
666
667 size_t ctr = 0;
668 char tmp[5];
2c510d90 669 for (auto const &c : val.content) {
786ed0ff
PL
670 snprintf(tmp, sizeof(tmp), "%02X", c);
671 d_string+=tmp;
672 ctr++;
673 if (ctr % 2 == 0 && ctr != 8) {
674 d_string+=':';
675 }
676 }
677}
678
341930bb 679void RecordTextWriter::xfr48BitInt(const uint64_t& val)
4192ca66
BH
680{
681 if(!d_string.empty())
682 d_string.append(1,' ');
335da0ba 683 d_string+=std::to_string(val);
4192ca66
BH
684}
685
20133c59 686
341930bb
BH
687void RecordTextWriter::xfr32BitInt(const uint32_t& val)
688{
689 if(!d_string.empty())
690 d_string.append(1,' ');
335da0ba 691 d_string+=std::to_string(val);
341930bb 692}
20133c59
BH
693
694void RecordTextWriter::xfrType(const uint16_t& val)
695{
696 if(!d_string.empty())
697 d_string.append(1,' ');
698 d_string+=DNSRecordContent::NumberToType(val);
699}
700
ec486449 701// this function is on the fast path for the pdns_recursor
cbf0e7f3
BH
702void RecordTextWriter::xfrIP(const uint32_t& val)
703{
704 if(!d_string.empty())
705 d_string.append(1,' ');
706
ec486449 707 char tmp[17];
79a9e9ad 708 uint32_t ip=val;
1149fa0b
BH
709 uint8_t vals[4];
710
711 memcpy(&vals[0], &ip, sizeof(ip));
712
713 char *pos=tmp;
714
715 for(int n=0; n < 4; ++n) {
716 if(vals[n]<10) {
717 *(pos++)=vals[n]+'0';
718 } else if(vals[n] < 100) {
719 *(pos++)=(vals[n]/10) +'0';
720 *(pos++)=(vals[n]%10) +'0';
721 } else {
722 *(pos++)=(vals[n]/100) +'0';
723 vals[n]%=100;
724 *(pos++)=(vals[n]/10) +'0';
725 *(pos++)=(vals[n]%10) +'0';
726 }
727 if(n!=3)
728 *(pos++)='.';
729 }
730 *pos=0;
731 d_string.append(tmp, pos);
cbf0e7f3
BH
732}
733
b9b28916
AT
734void RecordTextWriter::xfrIP6(const std::string& val)
735{
736 char tmpbuf[16];
737 char addrbuf[40];
738
739 if(!d_string.empty())
740 d_string.append(1,' ');
1e05b07c 741
b9b28916
AT
742 val.copy(tmpbuf,16);
743
4646277d 744 if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr)
b9b28916 745 throw RecordTextException("Unable to convert to ipv6 address");
1e05b07c 746
b9b28916
AT
747 d_string += std::string(addrbuf);
748}
cbf0e7f3 749
d73de874 750void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
f4352636
PD
751{
752 string ip = val.toString();
753
754 if(!d_string.empty())
755 d_string.append(1,' ');
756
757 d_string += ip;
758}
759
760void RecordTextWriter::xfrCAPort(ComboAddress &val)
761{
762 xfr16BitInt(val.sin4.sin_port);
763}
764
8bf26468
BH
765void RecordTextWriter::xfrTime(const uint32_t& val)
766{
767 if(!d_string.empty())
768 d_string.append(1,' ');
1e05b07c 769
8bf26468
BH
770 struct tm tm;
771 time_t time=val; // Y2038 bug!
c96765da 772 gmtime_r(&time, &tm);
76473b92 773
3cbe2773
CHB
774 static const boost::format fmt("%04d%02d%02d" "%02d%02d%02d");
775 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);
8bf26468
BH
776}
777
778
cbf0e7f3
BH
779void RecordTextWriter::xfr16BitInt(const uint16_t& val)
780{
781 xfr32BitInt(val);
782}
783
8c1c9170
BH
784void RecordTextWriter::xfr8BitInt(const uint8_t& val)
785{
786 xfr32BitInt(val);
787}
788
7ecd3576 789// should not mess with the escapes
d73de874 790void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */, bool /* noDot */)
4192ca66
BH
791{
792 if(!d_string.empty())
793 d_string.append(1,' ');
1e05b07c 794
f21fc0aa 795 if(d_nodot) {
3b295666 796 d_string+=val.toStringRootDot();
f21fc0aa
PD
797 }
798 else
799 {
800 d_string+=val.toString();
801 }
4192ca66
BH
802}
803
2fe9d6f7
AT
804void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size)
805{
806 xfrBlob(val, size);
807}
808
06ffdc52 809void RecordTextWriter::xfrBlob(const string& val, int)
8c1c9170
BH
810{
811 if(!d_string.empty())
812 d_string.append(1,' ');
813
814 d_string+=Base64Encode(val);
815}
816
e4090157 817void RecordTextWriter::xfrHexBlob(const string& val, bool)
59a0f653
BH
818{
819 if(!d_string.empty())
820 d_string.append(1,' ');
821
3c873e66
BH
822 if(val.empty()) {
823 d_string.append(1,'-');
824 return;
825 }
826
59a0f653
BH
827 string::size_type limit=val.size();
828 char tmp[5];
829 for(string::size_type n = 0; n < limit; ++n) {
9b2244e1 830 snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]);
59a0f653
BH
831 d_string+=tmp;
832 }
833}
834
b1a048a9
PL
835// FIXME copied from dnsparser.cc, see #6010 and #3503 if you want a proper solution
836static string txtEscape(const string &name)
837{
838 string ret;
839 char ebuf[5];
840
d7f67000
RP
841 for(char i : name) {
842 if((unsigned char) i >= 127 || (unsigned char) i < 32) {
843 snprintf(ebuf, sizeof(ebuf), "\\%03u", (unsigned char)i);
b1a048a9
PL
844 ret += ebuf;
845 }
d7f67000 846 else if(i=='"' || i=='\\'){
b1a048a9 847 ret += '\\';
d7f67000 848 ret += i;
b1a048a9
PL
849 }
850 else
d7f67000 851 ret += i;
b1a048a9
PL
852 }
853 return ret;
854}
855
a064bd5a
PL
856void RecordTextWriter::xfrSVCBValueList(const vector<string> &val) {
857 bool shouldQuote{false};
858 vector<string> escaped;
859 escaped.reserve(val.size());
860 for (auto const &v : val) {
861 if (v.find_first_of(' ') != string::npos) {
862 shouldQuote = true;
863 }
864 string tmp = txtEscape(v);
5597ee55
PL
865 string unescaped;
866 unescaped.reserve(tmp.size() + 4);
867 for (auto const &ch : tmp) {
868 if (ch == '\\') {
869 unescaped += R"F(\\)F";
870 continue;
871 }
872 if (ch == ',') {
873 unescaped += R"F(\\,)F";
874 continue;
875 }
876 unescaped += ch;
877 }
878 escaped.push_back(unescaped);
a064bd5a
PL
879 }
880 if (shouldQuote) {
881 d_string.append(1, '"');
882 }
883 d_string.append(boost::join(escaped, ","));
884 if (shouldQuote) {
885 d_string.append(1, '"');
886 }
887}
888
373914dc
PL
889void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
890 for (auto const &param : val) {
891 if (!d_string.empty())
892 d_string.append(1, ' ');
893
894 d_string.append(SvcParam::keyToString(param.getKey()));
895 if (param.getKey() != SvcParam::no_default_alpn) {
896 d_string.append(1, '=');
897 }
898
899 switch (param.getKey())
900 {
901 case SvcParam::no_default_alpn:
902 break;
903 case SvcParam::ipv4hint: /* fall-through */
904 case SvcParam::ipv6hint:
905 // TODO use xfrCA and put commas in between?
ed34c66a
PL
906 if (param.getAutoHint()) {
907 d_string.append("auto");
908 break;
909 }
373914dc
PL
910 d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
911 break;
912 case SvcParam::alpn:
a064bd5a 913 xfrSVCBValueList(param.getALPN());
373914dc
PL
914 break;
915 case SvcParam::mandatory:
916 {
917 bool doComma = false;
918 for (auto const &k: param.getMandatory()) {
919 if (doComma)
920 d_string.append(1, ',');
921 d_string.append(SvcParam::keyToString(k));
922 doComma = true;
923 }
924 break;
925 }
926 case SvcParam::port: {
927 auto str = d_string;
928 d_string.clear();
929 xfr16BitInt(param.getPort());
930 d_string = str + d_string;
931 break;
932 }
4f254e34 933 case SvcParam::ech: {
373914dc
PL
934 auto str = d_string;
935 d_string.clear();
4f254e34 936 xfrBlobNoSpaces(param.getECH());
373914dc
PL
937 d_string = str + '"' + d_string + '"';
938 break;
939 }
940 default:
b1a048a9
PL
941 auto str = d_string;
942 d_string.clear();
943 xfrText(param.getValue(), false, false);
944 d_string = str + '"' + txtEscape(d_string) + '"';
373914dc
PL
945 break;
946 }
947 }
948}
949
d73de874 950void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
4192ca66
BH
951{
952 if(!d_string.empty())
953 d_string.append(1,' ');
4192ca66 954
ef6a78d5 955 d_string.append(val);
4192ca66
BH
956}
957
d73de874 958void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
948a927f
PL
959{
960 if(!d_string.empty())
961 d_string.append(1,' ');
962 d_string.append(val);
963}
4192ca66
BH
964
965#ifdef TESTING
966
967int main(int argc, char**argv)
968try
969{
970 RecordTextReader rtr(argv[1], argv[2]);
1e05b07c 971
4192ca66
BH
972 unsigned int order, pref;
973 string flags, services, regexp, replacement;
974 string mx;
975
976 rtr.xfrInt(order);
977 rtr.xfrInt(pref);
cbf0e7f3
BH
978 rtr.xfrText(flags);
979 rtr.xfrText(services);
980 rtr.xfrText(regexp);
ad8fa726 981 rtr.xfrName(replacement);
4192ca66
BH
982
983 cout<<"order: "<<order<<", pref: "<<pref<<"\n";
984 cout<<"flags: \""<<flags<<"\", services: \""<<services<<"\", regexp: \""<<regexp<<"\", replacement: "<<replacement<<"\n";
985
986 string out;
987 RecordTextWriter rtw(out);
988
989 rtw.xfrInt(order);
990 rtw.xfrInt(pref);
cbf0e7f3
BH
991 rtw.xfrText(flags);
992 rtw.xfrText(services);
993 rtw.xfrText(regexp);
ad8fa726 994 rtw.xfrName(replacement);
4192ca66
BH
995
996 cout<<"Regenerated: '"<<out<<"'\n";
1e05b07c 997
4192ca66 998}
adc10f99 999catch(std::exception& e)
4192ca66
BH
1000{
1001 cerr<<"Fatal: "<<e.what()<<endl;
1002}
1003
1004#endif