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