]>
Commit | Line | Data |
---|---|---|
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 | 39 | RecordTextReader::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 |
47 | void 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 |
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 | ||
d97a781e | 69 | std::memcpy(&val.content, tmpbuf.s6_addr, sizeof(val.content)); |
786ed0ff PL |
70 | d_pos += len; |
71 | } | |
72 | ||
341930bb BH |
73 | void 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 | 87 | void 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 |
100 | void 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 |
130 | void 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 |
180 | void 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 |
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 | ||
20133c59 BH |
229 | bool RecordTextReader::eof() |
230 | { | |
231 | return d_pos==d_end; | |
232 | } | |
233 | ||
cbf0e7f3 BH |
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 | ||
8c1c9170 BH |
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 | ||
1e05b07c | 252 | // this code should leave all the escapes around |
f21fc0aa | 253 | void 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 | 281 | static 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 |
296 | void 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 | 313 | void 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 |
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 | ||
d1082642 PL |
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 | ||
373914dc PL |
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; | |
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 | |
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 | |
335da0ba | 516 | throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'"); |
59a0f653 BH |
517 | } |
518 | ||
519 | ||
050e6877 | 520 | static 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 | 547 | void 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 |
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 | ||
1c4d88c5 BH |
568 | void 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 | 577 | void 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 | 616 | void 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 |
631 | void 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 |
645 | void 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 | 655 | RecordTextWriter::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 |
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]; | |
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 | 679 | void 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 |
687 | void 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 | |
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 | ||
ec486449 | 701 | // this function is on the fast path for the pdns_recursor |
cbf0e7f3 BH |
702 | void 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 |
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,' '); | |
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 | 750 | void 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 | ||
760 | void RecordTextWriter::xfrCAPort(ComboAddress &val) | |
761 | { | |
762 | xfr16BitInt(val.sin4.sin_port); | |
763 | } | |
764 | ||
8bf26468 BH |
765 | void 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 |
779 | void RecordTextWriter::xfr16BitInt(const uint16_t& val) |
780 | { | |
781 | xfr32BitInt(val); | |
782 | } | |
783 | ||
8c1c9170 BH |
784 | void RecordTextWriter::xfr8BitInt(const uint8_t& val) |
785 | { | |
786 | xfr32BitInt(val); | |
787 | } | |
788 | ||
7ecd3576 | 789 | // should not mess with the escapes |
d73de874 | 790 | void 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 |
804 | void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size) |
805 | { | |
806 | xfrBlob(val, size); | |
807 | } | |
808 | ||
06ffdc52 | 809 | void 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 | 817 | void 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 |
836 | static 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 |
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); | |
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 |
889 | void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) { |
890 | for (auto const ¶m : 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 | 950 | void 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 | 958 | void 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 | ||
967 | int main(int argc, char**argv) | |
968 | try | |
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 | 999 | catch(std::exception& e) |
4192ca66 BH |
1000 | { |
1001 | cerr<<"Fatal: "<<e.what()<<endl; | |
1002 | } | |
1003 | ||
1004 | #endif |