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