]>
Commit | Line | Data |
---|---|---|
12471842 PL |
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 | */ | |
3c115e0f | 22 | #include "dnsname.hh" |
23 | #include <boost/format.hpp> | |
24 | #include <string> | |
27b19e8b | 25 | #include <cinttypes> |
6d8bc3c6 | 26 | |
3c115e0f | 27 | #include "dnswriter.hh" |
5e1031cd PD |
28 | #include "misc.hh" |
29 | ||
30 | #include <boost/functional/hash.hpp> | |
3c115e0f | 31 | |
c21484df | 32 | const DNSName g_rootdnsname("."), g_wildcarddnsname("*"); |
33 | ||
97e5c6bc | 34 | /* raw storage |
ae14c1f3 | 35 | in DNS label format, with trailing 0. W/o trailing 0, we are 'empty' |
36 | www.powerdns.com = 3www8powerdns3com0 | |
97e5c6bc | 37 | */ |
38 | ||
8171ab83 | 39 | std::ostream & operator<<(std::ostream &os, const DNSName& d) |
40 | { | |
39b3e0b2 | 41 | return os <<d.toLogString(); |
8171ab83 | 42 | } |
43 | ||
beb9fad5 | 44 | DNSName::DNSName(const char* p, size_t length) |
3c115e0f | 45 | { |
6c7983ed | 46 | if(p[0]==0 || (p[0]=='.' && p[1]==0)) { |
ae14c1f3 | 47 | d_storage.assign(1, (char)0); |
48 | } else { | |
bae1b0a2 | 49 | if(!strchr(p, '\\')) { |
50 | unsigned char lenpos=0; | |
51 | unsigned char labellen=0; | |
beb9fad5 | 52 | size_t plen=length; |
bae1b0a2 | 53 | const char* const pbegin=p, *pend=p+plen; |
54 | d_storage.reserve(plen+1); | |
55 | for(auto iter = pbegin; iter != pend; ) { | |
56 | lenpos = d_storage.size(); | |
57 | if(*iter=='.') | |
58 | throw std::runtime_error("Found . in wrong position in DNSName "+string(p)); | |
59 | d_storage.append(1, (char)0); | |
60 | labellen=0; | |
61 | auto begiter=iter; | |
62 | for(; iter != pend && *iter!='.'; ++iter) { | |
63 | labellen++; | |
64 | } | |
65 | d_storage.append(begiter,iter); | |
66 | if(iter != pend) | |
67 | ++iter; | |
68 | if(labellen > 63) | |
69 | throw std::range_error("label too long to append"); | |
70 | ||
71 | if(iter-pbegin > 254) // reserve two bytes, one for length and one for the root label | |
72 | throw std::range_error("name too long to append"); | |
73 | ||
74 | d_storage[lenpos]=labellen; | |
75 | } | |
76 | d_storage.append(1, (char)0); | |
77 | } | |
d0a4a4bc | 78 | else { |
beb9fad5 | 79 | d_storage=segmentDNSNameRaw(p, length); |
d0a4a4bc RG |
80 | if(d_storage.size() > 255) { |
81 | throw std::range_error("name too long"); | |
82 | } | |
83 | } | |
ae14c1f3 | 84 | } |
3c115e0f | 85 | } |
86 | ||
bae1b0a2 | 87 | |
83fc9d8a | 88 | DNSName::DNSName(const char* pos, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, uint16_t minOffset) |
3c115e0f | 89 | { |
adbace93 | 90 | if (offset >= len) |
e415d842 | 91 | throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")"); |
adbace93 | 92 | |
e14febcf | 93 | if(!uncompress) { |
94 | if(const void * fnd=memchr(pos+offset, 0, len-offset)) { | |
95 | d_storage.reserve(2+(const char*)fnd-(pos+offset)); | |
96 | } | |
97 | } | |
98 | ||
83fc9d8a | 99 | packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset); |
ac7921b0 | 100 | } |
101 | ||
102 | // this should be the __only__ dns name parser in PowerDNS. | |
83fc9d8a | 103 | void DNSName::packetParser(const char* qpos, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset) |
ac7921b0 | 104 | { |
9f652a9c | 105 | const unsigned char* pos=(const unsigned char*)qpos; |
3c115e0f | 106 | unsigned char labellen; |
9f652a9c | 107 | const unsigned char *opos = pos; |
3bf1f0b0 RG |
108 | |
109 | if (offset >= len) | |
e415d842 | 110 | throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")"); |
83fc9d8a RG |
111 | if (offset < (int) minOffset) |
112 | throw std::range_error("Trying to read before the beginning of the buffer ("+std::to_string(offset)+ " < "+std::to_string(minOffset)+")"); | |
3bf1f0b0 | 113 | |
9f652a9c | 114 | const unsigned char* end = pos + len; |
b43f60f3 | 115 | pos += offset; |
3c115e0f | 116 | while((labellen=*pos++) && pos < end) { // "scan and copy" |
99bbbc7b | 117 | if(labellen >= 0xc0) { |
b5b1018e | 118 | if(!uncompress) |
ac7921b0 | 119 | throw std::range_error("Found compressed label, instructed not to follow"); |
b5b1018e | 120 | |
121 | labellen &= (~0xc0); | |
122 | int newpos = (labellen << 8) + *(const unsigned char*)pos; | |
123 | ||
9114819c | 124 | if(newpos < offset) { |
83fc9d8a RG |
125 | if(newpos < (int) minOffset) |
126 | throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")"); | |
4c6ce4f1 | 127 | if (++depth > 100) |
9114819c | 128 | throw std::range_error("Abort label decompression after 100 redirects"); |
83fc9d8a | 129 | packetParser((const char*)opos, len, newpos, true, 0, 0, 0, depth, minOffset); |
9114819c | 130 | } else |
30826379 | 131 | throw std::range_error("Found a forward reference during label decompression"); |
b5b1018e | 132 | pos++; |
133 | break; | |
99bbbc7b RG |
134 | } else if(labellen & 0xc0) { |
135 | throw std::range_error("Found an invalid label length in qname (only one of the first two bits is set)"); | |
b5b1018e | 136 | } |
ac7921b0 | 137 | if (pos + labellen < end) { |
e14febcf | 138 | appendRawLabel((const char*)pos, labellen); |
ac7921b0 | 139 | } |
140 | else | |
141 | throw std::range_error("Found an invalid label length in qname"); | |
3c115e0f | 142 | pos+=labellen; |
143 | } | |
f867aa74 | 144 | if(d_storage.empty()) |
145 | d_storage.append(1, (char)0); // we just parsed the root | |
520eb5a0 | 146 | if(consumed) |
147 | *consumed = pos - opos - offset; | |
bd910269 | 148 | if(qtype) { |
7b9c052c RG |
149 | if (pos + 2 > end) { |
150 | throw std::range_error("Trying to read qtype past the end of the buffer ("+std::to_string((pos - opos) + 2)+ " > "+std::to_string(len)+")"); | |
bd910269 | 151 | } |
3c115e0f | 152 | *qtype=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1); |
bd910269 | 153 | } |
31b386e1 | 154 | pos+=2; |
bd910269 | 155 | if(qclass) { |
7b9c052c RG |
156 | if (pos + 2 > end) { |
157 | throw std::range_error("Trying to read qclass past the end of the buffer ("+std::to_string((pos - opos) + 2)+ " > "+std::to_string(len)+")"); | |
bd910269 | 158 | } |
31b386e1 | 159 | *qclass=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1); |
bd910269 | 160 | } |
3c115e0f | 161 | } |
162 | ||
a61e8e59 | 163 | std::string DNSName::toString(const std::string& separator, const bool trailing) const |
3c115e0f | 164 | { |
e1a9ab9f | 165 | if (empty()) { |
ae14c1f3 | 166 | throw std::out_of_range("Attempt to print an unset dnsname"); |
e1a9ab9f | 167 | } |
ae14c1f3 | 168 | |
f0da87e0 | 169 | if(isRoot()) |
07338ade KM |
170 | return trailing ? separator : ""; |
171 | ||
3c115e0f | 172 | std::string ret; |
f0da87e0 | 173 | ret.reserve(d_storage.size()); |
19164b93 CHB |
174 | |
175 | { | |
176 | // iterate over the raw labels | |
177 | const char* p = d_storage.c_str(); | |
178 | const char* end = p + d_storage.size(); | |
179 | ||
180 | while (p < end && *p) { | |
2430e53c RG |
181 | appendEscapedLabel(ret, p + 1, static_cast<size_t>(*p)); |
182 | ret += separator; | |
19164b93 CHB |
183 | p += *p + 1; |
184 | } | |
3c115e0f | 185 | } |
f0da87e0 CHB |
186 | if (!trailing) { |
187 | ret.resize(ret.size() - separator.size()); | |
188 | } | |
189 | return ret; | |
3c115e0f | 190 | } |
191 | ||
9ab84270 PD |
192 | std::string DNSName::toLogString() const |
193 | { | |
194 | if (empty()) { | |
195 | return "(empty)"; | |
196 | } | |
197 | ||
a724f955 | 198 | return toStringRootDot(); |
9ab84270 PD |
199 | } |
200 | ||
3c115e0f | 201 | std::string DNSName::toDNSString() const |
202 | { | |
ae14c1f3 | 203 | if (empty()) |
204 | throw std::out_of_range("Attempt to DNSString an unset dnsname"); | |
205 | ||
0ba4e1ee | 206 | return std::string(d_storage.c_str(), d_storage.length()); |
207 | } | |
208 | ||
209 | std::string DNSName::toDNSStringLC() const | |
210 | { | |
211 | return toLower(toDNSString()); // label lengths are always < 'A' | |
3c115e0f | 212 | } |
213 | ||
3155c04a PL |
214 | /** |
215 | * Get the length of the DNSName on the wire | |
216 | * | |
217 | * @return the total wirelength of the DNSName | |
218 | */ | |
219 | size_t DNSName::wirelength() const { | |
ae14c1f3 | 220 | return d_storage.length(); |
3155c04a PL |
221 | } |
222 | ||
ae14c1f3 | 223 | // Are WE part of parent |
3c115e0f | 224 | bool DNSName::isPartOf(const DNSName& parent) const |
225 | { | |
d44e4e09 | 226 | if(parent.empty() || empty()) |
ae14c1f3 | 227 | throw std::out_of_range("empty dnsnames aren't part of anything"); |
228 | ||
9e98d926 | 229 | if(parent.d_storage.size() > d_storage.size()) |
3bd38afa | 230 | return false; |
231 | ||
232 | // this is slightly complicated since we can't start from the end, since we can't see where a label begins/ends then | |
d8ec8f44 RG |
233 | for(auto us=d_storage.cbegin(); us<d_storage.cend(); us+=*us+1) { |
234 | auto distance = std::distance(us,d_storage.cend()); | |
235 | if (distance < 0 || static_cast<size_t>(distance) < parent.d_storage.size()) { | |
236 | break; | |
237 | } | |
238 | if (static_cast<size_t>(distance) == parent.d_storage.size()) { | |
3bd38afa | 239 | auto p = parent.d_storage.cbegin(); |
9e98d926 | 240 | for(; us != d_storage.cend(); ++us, ++p) { |
2b62292d | 241 | if(dns_tolower(*p) != dns_tolower(*us)) |
9e98d926 | 242 | return false; |
3bd38afa | 243 | } |
9e98d926 | 244 | return true; |
3bd38afa | 245 | } |
c9742dfb RG |
246 | if (*us < 0) { |
247 | throw std::out_of_range("negative label length in dnsname"); | |
248 | } | |
3c115e0f | 249 | } |
3bd38afa | 250 | return false; |
3c115e0f | 251 | } |
252 | ||
a61e8e59 KM |
253 | DNSName DNSName::makeRelative(const DNSName& zone) const |
254 | { | |
255 | DNSName ret(*this); | |
8ca15224 | 256 | ret.makeUsRelative(zone); |
07338ade | 257 | return ret.empty() ? zone : ret; // HACK FIXME400 |
a61e8e59 | 258 | } |
8ca15224 | 259 | void DNSName::makeUsRelative(const DNSName& zone) |
260 | { | |
261 | if (isPartOf(zone)) { | |
262 | d_storage.erase(d_storage.size()-zone.d_storage.size()); | |
ae14c1f3 | 263 | d_storage.append(1, (char)0); // put back the trailing 0 |
8ca15224 | 264 | } |
265 | else | |
266 | clear(); | |
267 | } | |
a61e8e59 | 268 | |
9b061cf5 RG |
269 | DNSName DNSName::getCommonLabels(const DNSName& other) const |
270 | { | |
271 | DNSName result; | |
272 | ||
273 | const std::vector<std::string> ours = getRawLabels(); | |
274 | const std::vector<std::string> others = other.getRawLabels(); | |
275 | ||
276 | for (size_t pos = 0; ours.size() > pos && others.size() > pos; pos++) { | |
097f8a42 RG |
277 | const std::string& ourLabel = ours.at(ours.size() - pos - 1); |
278 | const std::string& otherLabel = others.at(others.size() - pos - 1); | |
279 | ||
280 | if (!pdns_iequals(ourLabel, otherLabel)) { | |
9b061cf5 RG |
281 | break; |
282 | } | |
283 | ||
097f8a42 | 284 | result.prependRawLabel(ourLabel); |
9b061cf5 RG |
285 | } |
286 | ||
287 | return result; | |
288 | } | |
289 | ||
a61e8e59 KM |
290 | DNSName DNSName::labelReverse() const |
291 | { | |
292 | DNSName ret; | |
c2b55ab9 | 293 | |
e1a9ab9f | 294 | if(isRoot()) |
295 | return *this; // we don't create the root automatically below | |
c2b55ab9 | 296 | |
d44e4e09 | 297 | if (!empty()) { |
a61e8e59 KM |
298 | vector<string> l=getRawLabels(); |
299 | while(!l.empty()) { | |
300 | ret.appendRawLabel(l.back()); | |
301 | l.pop_back(); | |
302 | } | |
303 | } | |
304 | return ret; | |
305 | } | |
306 | ||
3c115e0f | 307 | void DNSName::appendRawLabel(const std::string& label) |
308 | { | |
e14febcf | 309 | appendRawLabel(label.c_str(), label.length()); |
310 | } | |
311 | ||
312 | void DNSName::appendRawLabel(const char* start, unsigned int length) | |
313 | { | |
314 | if(length==0) | |
e11dc8c8 | 315 | throw std::range_error("no such thing as an empty label to append"); |
e14febcf | 316 | if(length > 63) |
e11dc8c8 | 317 | throw std::range_error("label too long to append"); |
1106afd9 | 318 | if(d_storage.size() + length > 254) // reserve one byte for the label length |
e11dc8c8 | 319 | throw std::range_error("name too long to append"); |
ac7921b0 | 320 | |
ae14c1f3 | 321 | if(d_storage.empty()) { |
e14febcf | 322 | d_storage.append(1, (char)length); |
ae14c1f3 | 323 | } |
324 | else { | |
e14febcf | 325 | *d_storage.rbegin()=(char)length; |
ae14c1f3 | 326 | } |
e14febcf | 327 | d_storage.append(start, length); |
ae14c1f3 | 328 | d_storage.append(1, (char)0); |
3c115e0f | 329 | } |
330 | ||
331 | void DNSName::prependRawLabel(const std::string& label) | |
332 | { | |
564ec901 | 333 | if(label.empty()) |
e11dc8c8 | 334 | throw std::range_error("no such thing as an empty label to prepend"); |
be6fbc68 | 335 | if(label.size() > 63) |
e11dc8c8 | 336 | throw std::range_error("label too long to prepend"); |
1106afd9 | 337 | if(d_storage.size() + label.size() > 254) // reserve one byte for the label length |
e11dc8c8 | 338 | throw std::range_error("name too long to prepend"); |
be6fbc68 | 339 | |
ae14c1f3 | 340 | if(d_storage.empty()) |
341 | d_storage.append(1, (char)0); | |
342 | ||
d8b778ff | 343 | string_t prep(1, (char)label.size()); |
344 | prep.append(label.c_str(), label.size()); | |
97e5c6bc | 345 | d_storage = prep+d_storage; |
3c115e0f | 346 | } |
347 | ||
ddb7e6c6 | 348 | bool DNSName::slowCanonCompare(const DNSName& rhs) const |
349 | { | |
350 | auto ours=getRawLabels(), rhsLabels = rhs.getRawLabels(); | |
351 | return std::lexicographical_compare(ours.rbegin(), ours.rend(), rhsLabels.rbegin(), rhsLabels.rend(), CIStringCompare()); | |
352 | } | |
353 | ||
19164b93 | 354 | vector<std::string> DNSName::getRawLabels() const |
3c115e0f | 355 | { |
19164b93 | 356 | vector<std::string> ret; |
ec3f33c6 | 357 | ret.reserve(countLabels()); |
ae14c1f3 | 358 | // 3www4ds9a2nl0 |
e358efcb RG |
359 | for(const unsigned char* p = (const unsigned char*) d_storage.c_str(); p < ((const unsigned char*) d_storage.c_str()) + d_storage.size() && *p; p+=*p+1) { |
360 | ret.push_back({(const char*)p+1, (size_t)*p}); // XXX FIXME | |
361 | } | |
97e5c6bc | 362 | return ret; |
3c115e0f | 363 | } |
364 | ||
39c9bef5 RG |
365 | std::string DNSName::getRawLabel(unsigned int pos) const |
366 | { | |
367 | unsigned int currentPos = 0; | |
368 | for(const unsigned char* p = (const unsigned char*) d_storage.c_str(); p < ((const unsigned char*) d_storage.c_str()) + d_storage.size() && *p; p+=*p+1, currentPos++) { | |
369 | if (currentPos == pos) { | |
370 | return std::string((const char*)p+1, (size_t)*p); | |
371 | } | |
372 | } | |
373 | ||
374 | throw std::out_of_range("trying to get label at position "+std::to_string(pos)+" of a DNSName that only has "+std::to_string(currentPos)+" labels"); | |
375 | } | |
6d8bc3c6 | 376 | |
41e01592 PL |
377 | DNSName DNSName::getLastLabel() const |
378 | { | |
379 | DNSName ret(*this); | |
380 | ret.trimToLabels(1); | |
381 | return ret; | |
382 | } | |
383 | ||
21a3792f | 384 | bool DNSName::chopOff() |
3c115e0f | 385 | { |
ae14c1f3 | 386 | if(d_storage.empty() || d_storage[0]==0) |
3c115e0f | 387 | return false; |
ec3f33c6 | 388 | d_storage.erase(0, (unsigned int)d_storage[0]+1); |
3c115e0f | 389 | return true; |
390 | } | |
391 | ||
8aa5a28c KM |
392 | bool DNSName::isWildcard() const |
393 | { | |
07338ade | 394 | if(d_storage.size() < 2) |
8aa5a28c KM |
395 | return false; |
396 | auto p = d_storage.begin(); | |
397 | return (*p == 0x01 && *++p == '*'); | |
398 | } | |
399 | ||
32cd4eb1 PL |
400 | /* |
401 | * Returns true if the DNSName is a valid RFC 1123 hostname, this function uses | |
402 | * a regex on the string, so it is probably best not used when speed is essential. | |
403 | */ | |
404 | bool DNSName::isHostname() const | |
405 | { | |
406 | static Regex hostNameRegex = Regex("^(([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)\\.)+$"); | |
407 | return hostNameRegex.match(this->toString()); | |
408 | } | |
409 | ||
97e5c6bc | 410 | unsigned int DNSName::countLabels() const |
411 | { | |
412 | unsigned int count=0; | |
19164b93 CHB |
413 | const unsigned char* p = reinterpret_cast<const unsigned char*>(d_storage.c_str()); |
414 | const unsigned char* end = reinterpret_cast<const unsigned char*>(p + d_storage.size()); | |
415 | ||
416 | while (p < end && *p) { | |
97e5c6bc | 417 | ++count; |
19164b93 CHB |
418 | p += *p + 1; |
419 | } | |
97e5c6bc | 420 | return count; |
421 | } | |
422 | ||
c9262563 | 423 | void DNSName::trimToLabels(unsigned int to) |
424 | { | |
97e5c6bc | 425 | while(countLabels() > to && chopOff()) |
c9262563 | 426 | ; |
427 | } | |
428 | ||
3c115e0f | 429 | |
5e1031cd PD |
430 | size_t hash_value(DNSName const& d) |
431 | { | |
76cca09f | 432 | return d.hash(); |
5e1031cd PD |
433 | } |
434 | ||
2430e53c | 435 | void DNSName::appendEscapedLabel(std::string& appendTo, const char* orig, size_t len) |
3c115e0f | 436 | { |
19164b93 CHB |
437 | size_t pos = 0; |
438 | ||
19164b93 CHB |
439 | while (pos < len) { |
440 | auto p = static_cast<uint8_t>(orig[pos]); | |
950cfe0f | 441 | if(p=='.') |
2430e53c | 442 | appendTo+="\\."; |
3c115e0f | 443 | else if(p=='\\') |
2430e53c | 444 | appendTo+="\\\\"; |
568556f8 | 445 | else if(p > 0x20 && p < 0x7f) |
2430e53c | 446 | appendTo.append(1, (char)p); |
3c115e0f | 447 | else { |
2430e53c RG |
448 | char buf[] = "000"; |
449 | auto got = snprintf(buf, sizeof(buf), "%03" PRIu8, p); | |
450 | if (got < 0 || static_cast<size_t>(got) >= sizeof(buf)) { | |
451 | throw std::runtime_error("Error, snprintf returned " + std::to_string(got) + " while escaping label " + std::string(orig, len)); | |
452 | } | |
453 | appendTo.append(1, '\\'); | |
454 | appendTo += buf; | |
3c115e0f | 455 | } |
19164b93 | 456 | ++pos; |
3c115e0f | 457 | } |
3c115e0f | 458 | } |
bf7ef5b4 RG |
459 | |
460 | bool DNSName::has8bitBytes() const | |
461 | { | |
462 | const auto& s = d_storage; | |
463 | string::size_type pos = 0; | |
464 | uint8_t length = s.at(pos); | |
465 | while (length > 0) { | |
466 | for (size_t idx = 0; idx < length; idx++) { | |
467 | ++pos; | |
468 | char c = s.at(pos); | |
469 | if(!((c >= 'a' && c <= 'z') || | |
470 | (c >= 'A' && c <= 'Z') || | |
471 | (c >= '0' && c <= '9') || | |
472 | c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@' || c==' ' || c=='\\' || c==':')) | |
473 | return true; | |
474 | } | |
475 | ++pos; | |
476 | length = s.at(pos); | |
477 | } | |
478 | ||
479 | return false; | |
480 | } |