]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/rcpgenerator.cc
Merge pull request #14032 from rgacogne/ddist-192-changelog-secpoll
[thirdparty/pdns.git] / pdns / rcpgenerator.cc
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, DNSName zone) : d_string(std::move(str)), d_zone(std::move(zone)), d_pos(0)
40 {
41 /* remove whitespace */
42 if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) ))
43 boost::trim_if(d_string, dns_isspace);
44 d_end = d_string.size();
45 }
46
47 void RecordTextReader::xfr48BitInt(uint64_t &val)
48 {
49 xfr64BitInt(val);
50 if (val > 281474976710655LL)
51 throw RecordTextException("Overflow reading 48 bit integer from record content"); // fixme improve
52 }
53
54 void 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
69 std::memcpy(&val.content, tmpbuf.s6_addr, sizeof(val.content));
70 d_pos += len;
71 }
72
73 void RecordTextReader::xfr64BitInt(uint64_t &val)
74 {
75 skipSpaces();
76
77 if(!isdigit(d_string.at(d_pos)))
78 throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
79
80 size_t pos;
81 val=std::stoull(d_string.substr(d_pos), &pos);
82
83 d_pos += pos;
84 }
85
86
87 void RecordTextReader::xfr32BitInt(uint32_t &val)
88 {
89 skipSpaces();
90
91 if(!isdigit(d_string.at(d_pos)))
92 throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
93
94 size_t pos;
95 val = pdns::checked_stoi<uint32_t>(d_string.c_str() + d_pos, &pos);
96
97 d_pos += pos;
98 }
99
100 void RecordTextReader::xfrTime(uint32_t &val)
101 {
102 struct tm tm;
103 memset(&tm, 0, sizeof(tm));
104
105 uint64_t itmp;
106 xfr64BitInt(itmp);
107
108 if (itmp <= (uint32_t)~0) {
109 // formatted as seconds since epoch, not as YYYYMMDDHHmmSS:
110 val = (uint32_t) itmp;
111 return;
112 }
113
114 ostringstream tmp;
115
116 tmp<<itmp;
117
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 }
123
124 tm.tm_year-=1900;
125 tm.tm_mon-=1;
126 // coverity[store_truncates_time_t]
127 val=(uint32_t)Utility::timegm(&tm);
128 }
129
130 void RecordTextReader::xfrIP(uint32_t &val)
131 {
132 skipSpaces();
133
134 if(!isdigit(d_string.at(d_pos)))
135 throw RecordTextException("while parsing IP address, expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
136
137 uint32_t octet=0;
138 val=0;
139 char count=0;
140 bool last_was_digit = false;
141
142 for(;;) {
143 if(d_string.at(d_pos)=='.') {
144 if (!last_was_digit)
145 throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
146 last_was_digit = false;
147 val<<=8;
148 val+=octet;
149 octet=0;
150 count++;
151 if(count > 3)
152 throw RecordTextException(string("unable to parse IP address, too many dots"));
153 }
154 else if(isdigit(d_string.at(d_pos))) {
155 last_was_digit = true;
156 octet*=10;
157 octet+=d_string.at(d_pos) - '0';
158 if(octet > 255)
159 throw RecordTextException("unable to parse IP address");
160 }
161 else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',')
162 break;
163 else {
164 throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
165 }
166 d_pos++;
167 if(d_pos == d_string.length())
168 break;
169 }
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;
176 val=ntohl(val);
177 }
178
179
180 void RecordTextReader::xfrIP6(std::string &val)
181 {
182 struct in6_addr tmpbuf;
183
184 skipSpaces();
185
186 size_t len;
187 // lookup end of value - think of ::ffff encoding too, has dots in it!
188 for(len=0;
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)=='.');
190 len++);
191
192 if(!len)
193 throw RecordTextException("while parsing IPv6 address, expected xdigits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
194
195 // end of value is here, try parse as IPv6
196 string address=d_string.substr(d_pos, len);
197
198 if (inet_pton(AF_INET6, address.c_str(), &tmpbuf) != 1) {
199 throw RecordTextException("while parsing IPv6 address: '" + address + "' is invalid");
200 }
201
202 val = std::string((char*)tmpbuf.s6_addr, 16);
203
204 d_pos += len;
205 }
206
207 void 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
222 void RecordTextReader::xfrCAPort(ComboAddress &val)
223 {
224 uint16_t port;
225 xfr16BitInt(port);
226 val.sin4.sin_port = port;
227 }
228
229 bool RecordTextReader::eof()
230 {
231 return d_pos==d_end;
232 }
233
234 void 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
243 void 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
252 // this code should leave all the escapes around
253 void RecordTextReader::xfrName(DNSName& val, bool, bool)
254 {
255 skipSpaces();
256 DNSName sval;
257
258 string::size_type begin_pos = d_pos;
259 while (d_pos < d_end) {
260 if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
261 break;
262 }
263
264 d_pos++;
265 }
266
267 {
268 std::string_view view(d_string);
269 sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
270 }
271
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);
279 }
280
281 static bool isbase64(char c, bool acceptspace)
282 {
283 if(dns_isspace(c))
284 return acceptspace;
285 if(c >= '0' && c <= '9')
286 return true;
287 if(c >= 'a' && c <= 'z')
288 return true;
289 if(c >= 'A' && c <= 'Z')
290 return true;
291 if(c=='+' || c=='/' || c=='=')
292 return true;
293 return false;
294 }
295
296 void RecordTextReader::xfrBlobNoSpaces(string& val, int len) {
297 skipSpaces();
298 int pos=(int)d_pos;
299 const char* strptr=d_string.c_str();
300 while(d_pos < d_end && isbase64(strptr[d_pos], false))
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);
308
309 if (len>-1 && val.size() != static_cast<size_t>(len))
310 throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len));
311 }
312
313 void RecordTextReader::xfrBlob(string& val, int)
314 {
315 skipSpaces();
316 int pos=(int)d_pos;
317 const char* strptr=d_string.c_str();
318 while(d_pos < d_end && isbase64(strptr[d_pos], true))
319 d_pos++;
320
321 string tmp;
322 tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
323 boost::erase_all(tmp," ");
324 val.clear();
325 B64Decode(tmp, val);
326 }
327
328 void RecordTextReader::xfrRFC1035CharString(string &val) {
329 auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val);
330 d_pos += ctr;
331 }
332
333 void 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
338 void 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;
357 bool generic;
358 try {
359 key = SvcParam::keyFromString(k, generic);
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;
384 bool doAuto{false};
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);
399 for (auto const &v: value) {
400 if (v == "auto") {
401 doAuto = true;
402 hints.clear();
403 break;
404 }
405 hints.push_back(ComboAddress(v));
406 }
407 }
408 try {
409 auto p = SvcParam(key, std::move(hints));
410 p.setAutoHint(doAuto);
411 val.insert(p);
412 }
413 catch (const std::invalid_argument& e) {
414 throw RecordTextException(e.what());
415 }
416 break;
417 }
418 case SvcParam::alpn: {
419 vector<string> value;
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 }
436 val.insert(SvcParam(key, std::move(value)));
437 break;
438 }
439 case SvcParam::mandatory: {
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 }
455 vector<string> parts;
456 xfrSVCBValueList(parts);
457 set<string> values(parts.begin(), parts.end());
458 val.insert(SvcParam(key, std::move(values)));
459 break;
460 }
461 case SvcParam::port: {
462 uint16_t port;
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 }
474 val.insert(SvcParam(key, port));
475 break;
476 }
477 case SvcParam::ech: {
478 string value;
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++;
492 }
493 }
494 val.insert(SvcParam(key, value));
495 break;
496 }
497 default: {
498 string value;
499 xfrRFC1035CharString(value);
500 val.insert(SvcParam(key, value));
501 break;
502 }
503 }
504 }
505 }
506
507 static 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
516 throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'");
517 }
518
519
520 static void HEXDecode(const char* begin, const char* end, string& out)
521 {
522 if(end - begin == 1 && *begin=='-') {
523 out.clear();
524 return;
525 }
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 {
536 val += hextodec(*begin);
537 out.append(1, (char) val);
538 mode = 0;
539 val = 0;
540 }
541 }
542 if(mode)
543 out.append(1, (char) val);
544
545 }
546
547 void RecordTextReader::xfrHexBlob(string& val, bool keepReading)
548 {
549 skipSpaces();
550 int pos=(int)d_pos;
551 while(d_pos < d_end && (keepReading || !dns_isspace(d_string[d_pos])))
552 d_pos++;
553
554 HEXDecode(d_string.c_str()+pos, d_string.c_str() + d_pos, val);
555 }
556
557 void 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
568 void RecordTextWriter::xfrBase32HexBlob(const string& val)
569 {
570 if(!d_string.empty())
571 d_string.append(1,' ');
572
573 d_string.append(toUpper(toBase32Hex(val)));
574 }
575
576
577 void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */)
578 {
579 val.clear();
580 val.reserve(d_end - d_pos);
581
582 while(d_pos != d_end) {
583 if(!val.empty())
584 val.append(1, ' ');
585
586 skipSpaces();
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 }
598 throw RecordTextException("Data field in DNS should start with quote (\") at position "+std::to_string(d_pos)+" of '"+d_string+"'");
599 }
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) {
603 val.append(1, d_string[d_pos++]);
604 }
605 val.append(1, d_string[d_pos]);
606 }
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;
613 }
614 }
615
616 void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
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
631 void RecordTextReader::xfrType(uint16_t& val)
632 {
633 skipSpaces();
634 int pos=(int)d_pos;
635 while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
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 }
643
644
645 void RecordTextReader::skipSpaces()
646 {
647 const char* strptr = d_string.c_str();
648 while(d_pos < d_end && dns_isspace(strptr[d_pos]))
649 d_pos++;
650 if(d_pos == d_end)
651 throw RecordTextException("missing field at the end of record content '"+d_string+"'");
652 }
653
654
655 RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str)
656 {
657 d_string.clear();
658 d_nodot=noDot;
659 }
660
661 void 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];
669 for (auto const &c : val.content) {
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
679 void RecordTextWriter::xfr48BitInt(const uint64_t& val)
680 {
681 if(!d_string.empty())
682 d_string.append(1,' ');
683 d_string+=std::to_string(val);
684 }
685
686
687 void RecordTextWriter::xfr32BitInt(const uint32_t& val)
688 {
689 if(!d_string.empty())
690 d_string.append(1,' ');
691 d_string+=std::to_string(val);
692 }
693
694 void 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
701 // this function is on the fast path for the pdns_recursor
702 void RecordTextWriter::xfrIP(const uint32_t& val)
703 {
704 if(!d_string.empty())
705 d_string.append(1,' ');
706
707 char tmp[17];
708 uint32_t ip=val;
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);
732 }
733
734 void 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,' ');
741
742 val.copy(tmpbuf,16);
743
744 if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr)
745 throw RecordTextException("Unable to convert to ipv6 address");
746
747 d_string += std::string(addrbuf);
748 }
749
750 void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
751 {
752 string ip = val.toString();
753
754 if(!d_string.empty())
755 d_string.append(1,' ');
756
757 d_string += ip;
758 }
759
760 void RecordTextWriter::xfrCAPort(ComboAddress &val)
761 {
762 xfr16BitInt(val.sin4.sin_port);
763 }
764
765 void RecordTextWriter::xfrTime(const uint32_t& val)
766 {
767 if(!d_string.empty())
768 d_string.append(1,' ');
769
770 struct tm tm;
771 time_t time=val; // Y2038 bug!
772 gmtime_r(&time, &tm);
773
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);
776 }
777
778
779 void RecordTextWriter::xfr16BitInt(const uint16_t& val)
780 {
781 xfr32BitInt(val);
782 }
783
784 void RecordTextWriter::xfr8BitInt(const uint8_t& val)
785 {
786 xfr32BitInt(val);
787 }
788
789 // should not mess with the escapes
790 void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */, bool /* noDot */)
791 {
792 if(!d_string.empty())
793 d_string.append(1,' ');
794
795 if(d_nodot) {
796 d_string+=val.toStringRootDot();
797 }
798 else
799 {
800 d_string+=val.toString();
801 }
802 }
803
804 void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size)
805 {
806 xfrBlob(val, size);
807 }
808
809 void RecordTextWriter::xfrBlob(const string& val, int)
810 {
811 if(!d_string.empty())
812 d_string.append(1,' ');
813
814 d_string+=Base64Encode(val);
815 }
816
817 void RecordTextWriter::xfrHexBlob(const string& val, bool)
818 {
819 if(!d_string.empty())
820 d_string.append(1,' ');
821
822 if(val.empty()) {
823 d_string.append(1,'-');
824 return;
825 }
826
827 string::size_type limit=val.size();
828 char tmp[5];
829 for(string::size_type n = 0; n < limit; ++n) {
830 snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]);
831 d_string+=tmp;
832 }
833 }
834
835 // FIXME copied from dnsparser.cc, see #6010 and #3503 if you want a proper solution
836 static string txtEscape(const string &name)
837 {
838 string ret;
839 char ebuf[5];
840
841 for(char i : name) {
842 if((unsigned char) i >= 127 || (unsigned char) i < 32) {
843 snprintf(ebuf, sizeof(ebuf), "\\%03u", (unsigned char)i);
844 ret += ebuf;
845 }
846 else if(i=='"' || i=='\\'){
847 ret += '\\';
848 ret += i;
849 }
850 else
851 ret += i;
852 }
853 return ret;
854 }
855
856 void 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);
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);
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
889 void 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?
906 if (param.getAutoHint()) {
907 d_string.append("auto");
908 break;
909 }
910 d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
911 break;
912 case SvcParam::alpn:
913 xfrSVCBValueList(param.getALPN());
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 }
933 case SvcParam::ech: {
934 auto str = d_string;
935 d_string.clear();
936 xfrBlobNoSpaces(param.getECH());
937 d_string = str + '"' + d_string + '"';
938 break;
939 }
940 default:
941 auto str = d_string;
942 d_string.clear();
943 xfrText(param.getValue(), false, false);
944 d_string = str + '"' + txtEscape(d_string) + '"';
945 break;
946 }
947 }
948 }
949
950 void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
951 {
952 if(!d_string.empty())
953 d_string.append(1,' ');
954
955 d_string.append(val);
956 }
957
958 void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
959 {
960 if(!d_string.empty())
961 d_string.append(1,' ');
962 d_string.append(val);
963 }
964
965 #ifdef TESTING
966
967 int main(int argc, char**argv)
968 try
969 {
970 RecordTextReader rtr(argv[1], argv[2]);
971
972 unsigned int order, pref;
973 string flags, services, regexp, replacement;
974 string mx;
975
976 rtr.xfrInt(order);
977 rtr.xfrInt(pref);
978 rtr.xfrText(flags);
979 rtr.xfrText(services);
980 rtr.xfrText(regexp);
981 rtr.xfrName(replacement);
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);
991 rtw.xfrText(flags);
992 rtw.xfrText(services);
993 rtw.xfrText(regexp);
994 rtw.xfrName(replacement);
995
996 cout<<"Regenerated: '"<<out<<"'\n";
997
998 }
999 catch(std::exception& e)
1000 {
1001 cerr<<"Fatal: "<<e.what()<<endl;
1002 }
1003
1004 #endif