]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsname.cc
Better (actual) fix for leak reported by Coverity.
[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 #include <cinttypes>
26
27 #include "dnswriter.hh"
28 #include "misc.hh"
29
30 #include <boost/functional/hash.hpp>
31
32 const DNSName g_rootdnsname("."), g_wildcarddnsname("*");
33
34 /* raw storage
35 in DNS label format, with trailing 0. W/o trailing 0, we are 'empty'
36 www.powerdns.com = 3www8powerdns3com0
37 */
38
39 std::ostream & operator<<(std::ostream &os, const DNSName& d)
40 {
41 return os <<d.toLogString();
42 }
43
44 DNSName::DNSName(const char* p, size_t length)
45 {
46 if(p[0]==0 || (p[0]=='.' && p[1]==0)) {
47 d_storage.assign(1, (char)0);
48 } else {
49 if(!strchr(p, '\\')) {
50 unsigned char lenpos=0;
51 unsigned char labellen=0;
52 size_t plen=length;
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 }
78 else {
79 d_storage=segmentDNSNameRaw(p, length);
80 if(d_storage.size() > 255) {
81 throw std::range_error("name too long");
82 }
83 }
84 }
85 }
86
87
88 DNSName::DNSName(const char* pos, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, uint16_t minOffset)
89 {
90 if (offset >= len)
91 throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
92
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
99 packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset);
100 }
101
102 // this should be the __only__ dns name parser in PowerDNS.
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)
104 {
105 const unsigned char* pos=(const unsigned char*)qpos;
106 unsigned char labellen;
107 const unsigned char *opos = pos;
108
109 if (offset >= len)
110 throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
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)+")");
113
114 const unsigned char* end = pos + len;
115 pos += offset;
116 while((labellen=*pos++) && pos < end) { // "scan and copy"
117 if(labellen >= 0xc0) {
118 if(!uncompress)
119 throw std::range_error("Found compressed label, instructed not to follow");
120
121 labellen &= (~0xc0);
122 int newpos = (labellen << 8) + *(const unsigned char*)pos;
123
124 if(newpos < offset) {
125 if(newpos < (int) minOffset)
126 throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")");
127 if (++depth > 100)
128 throw std::range_error("Abort label decompression after 100 redirects");
129 packetParser((const char*)opos, len, newpos, true, 0, 0, 0, depth, minOffset);
130 } else
131 throw std::range_error("Found a forward reference during label decompression");
132 pos++;
133 break;
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)");
136 }
137 if (pos + labellen < end) {
138 appendRawLabel((const char*)pos, labellen);
139 }
140 else
141 throw std::range_error("Found an invalid label length in qname");
142 pos+=labellen;
143 }
144 if(d_storage.empty())
145 d_storage.append(1, (char)0); // we just parsed the root
146 if(consumed)
147 *consumed = pos - opos - offset;
148 if(qtype) {
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)+")");
151 }
152 *qtype=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1);
153 }
154 pos+=2;
155 if(qclass) {
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)+")");
158 }
159 *qclass=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1);
160 }
161 }
162
163 std::string DNSName::toString(const std::string& separator, const bool trailing) const
164 {
165 if (empty()) {
166 throw std::out_of_range("Attempt to print an unset dnsname");
167 }
168
169 if(isRoot())
170 return trailing ? separator : "";
171
172 std::string ret;
173 ret.reserve(d_storage.size());
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) {
181 appendEscapedLabel(ret, p + 1, static_cast<size_t>(*p));
182 ret += separator;
183 p += *p + 1;
184 }
185 }
186 if (!trailing) {
187 ret.resize(ret.size() - separator.size());
188 }
189 return ret;
190 }
191
192 std::string DNSName::toLogString() const
193 {
194 if (empty()) {
195 return "(empty)";
196 }
197
198 return toStringRootDot();
199 }
200
201 std::string DNSName::toDNSString() const
202 {
203 if (empty())
204 throw std::out_of_range("Attempt to DNSString an unset dnsname");
205
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'
212 }
213
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 {
220 return d_storage.length();
221 }
222
223 // Are WE part of parent
224 bool DNSName::isPartOf(const DNSName& parent) const
225 {
226 if(parent.empty() || empty())
227 throw std::out_of_range("empty dnsnames aren't part of anything");
228
229 if(parent.d_storage.size() > d_storage.size())
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
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()) {
239 auto p = parent.d_storage.cbegin();
240 for(; us != d_storage.cend(); ++us, ++p) {
241 if(dns_tolower(*p) != dns_tolower(*us))
242 return false;
243 }
244 return true;
245 }
246 if (*us < 0) {
247 throw std::out_of_range("negative label length in dnsname");
248 }
249 }
250 return false;
251 }
252
253 DNSName DNSName::makeRelative(const DNSName& zone) const
254 {
255 DNSName ret(*this);
256 ret.makeUsRelative(zone);
257 return ret.empty() ? zone : ret; // HACK FIXME400
258 }
259 void DNSName::makeUsRelative(const DNSName& zone)
260 {
261 if (isPartOf(zone)) {
262 d_storage.erase(d_storage.size()-zone.d_storage.size());
263 d_storage.append(1, (char)0); // put back the trailing 0
264 }
265 else
266 clear();
267 }
268
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++) {
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)) {
281 break;
282 }
283
284 result.prependRawLabel(ourLabel);
285 }
286
287 return result;
288 }
289
290 DNSName DNSName::labelReverse() const
291 {
292 DNSName ret;
293
294 if(isRoot())
295 return *this; // we don't create the root automatically below
296
297 if (!empty()) {
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
307 void DNSName::appendRawLabel(const std::string& label)
308 {
309 appendRawLabel(label.c_str(), label.length());
310 }
311
312 void DNSName::appendRawLabel(const char* start, unsigned int length)
313 {
314 if(length==0)
315 throw std::range_error("no such thing as an empty label to append");
316 if(length > 63)
317 throw std::range_error("label too long to append");
318 if(d_storage.size() + length > 254) // reserve one byte for the label length
319 throw std::range_error("name too long to append");
320
321 if(d_storage.empty()) {
322 d_storage.append(1, (char)length);
323 }
324 else {
325 *d_storage.rbegin()=(char)length;
326 }
327 d_storage.append(start, length);
328 d_storage.append(1, (char)0);
329 }
330
331 void DNSName::prependRawLabel(const std::string& label)
332 {
333 if(label.empty())
334 throw std::range_error("no such thing as an empty label to prepend");
335 if(label.size() > 63)
336 throw std::range_error("label too long to prepend");
337 if(d_storage.size() + label.size() > 254) // reserve one byte for the label length
338 throw std::range_error("name too long to prepend");
339
340 if(d_storage.empty())
341 d_storage.append(1, (char)0);
342
343 string_t prep(1, (char)label.size());
344 prep.append(label.c_str(), label.size());
345 d_storage = prep+d_storage;
346 }
347
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
354 vector<std::string> DNSName::getRawLabels() const
355 {
356 vector<std::string> ret;
357 ret.reserve(countLabels());
358 // 3www4ds9a2nl0
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 }
362 return ret;
363 }
364
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 }
376
377 DNSName DNSName::getLastLabel() const
378 {
379 DNSName ret(*this);
380 ret.trimToLabels(1);
381 return ret;
382 }
383
384 bool DNSName::chopOff()
385 {
386 if(d_storage.empty() || d_storage[0]==0)
387 return false;
388 d_storage.erase(0, (unsigned int)d_storage[0]+1);
389 return true;
390 }
391
392 bool DNSName::isWildcard() const
393 {
394 if(d_storage.size() < 2)
395 return false;
396 auto p = d_storage.begin();
397 return (*p == 0x01 && *++p == '*');
398 }
399
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
410 unsigned int DNSName::countLabels() const
411 {
412 unsigned int count=0;
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) {
417 ++count;
418 p += *p + 1;
419 }
420 return count;
421 }
422
423 void DNSName::trimToLabels(unsigned int to)
424 {
425 while(countLabels() > to && chopOff())
426 ;
427 }
428
429
430 size_t hash_value(DNSName const& d)
431 {
432 return d.hash();
433 }
434
435 void DNSName::appendEscapedLabel(std::string& appendTo, const char* orig, size_t len)
436 {
437 size_t pos = 0;
438
439 while (pos < len) {
440 auto p = static_cast<uint8_t>(orig[pos]);
441 if(p=='.')
442 appendTo+="\\.";
443 else if(p=='\\')
444 appendTo+="\\\\";
445 else if(p > 0x20 && p < 0x7f)
446 appendTo.append(1, (char)p);
447 else {
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;
455 }
456 ++pos;
457 }
458 }
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 }