]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsname.cc
all: DNSName reserve memory before 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 for(const auto& s : getRawLabels()) {
174 ret+= escapeLabel(s) + separator;
175 }
176 if (!trailing) {
177 ret.resize(ret.size() - separator.size());
178 }
179 return ret;
180 }
181
182 std::string DNSName::toLogString() const
183 {
184 if (empty()) {
185 return "(empty)";
186 }
187
188 return toStringRootDot();
189 }
190
191 std::string DNSName::toDNSString() const
192 {
193 if (empty())
194 throw std::out_of_range("Attempt to DNSString an unset dnsname");
195
196 return std::string(d_storage.c_str(), d_storage.length());
197 }
198
199 std::string DNSName::toDNSStringLC() const
200 {
201 return toLower(toDNSString()); // label lengths are always < 'A'
202 }
203
204 /**
205 * Get the length of the DNSName on the wire
206 *
207 * @return the total wirelength of the DNSName
208 */
209 size_t DNSName::wirelength() const {
210 return d_storage.length();
211 }
212
213 // Are WE part of parent
214 bool DNSName::isPartOf(const DNSName& parent) const
215 {
216 if(parent.empty() || empty())
217 throw std::out_of_range("empty dnsnames aren't part of anything");
218
219 if(parent.d_storage.size() > d_storage.size())
220 return false;
221
222 // this is slightly complicated since we can't start from the end, since we can't see where a label begins/ends then
223 for(auto us=d_storage.cbegin(); us<d_storage.cend(); us+=*us+1) {
224 auto distance = std::distance(us,d_storage.cend());
225 if (distance < 0 || static_cast<size_t>(distance) < parent.d_storage.size()) {
226 break;
227 }
228 if (static_cast<size_t>(distance) == parent.d_storage.size()) {
229 auto p = parent.d_storage.cbegin();
230 for(; us != d_storage.cend(); ++us, ++p) {
231 if(dns_tolower(*p) != dns_tolower(*us))
232 return false;
233 }
234 return true;
235 }
236 if (*us < 0) {
237 throw std::out_of_range("negative label length in dnsname");
238 }
239 }
240 return false;
241 }
242
243 DNSName DNSName::makeRelative(const DNSName& zone) const
244 {
245 DNSName ret(*this);
246 ret.makeUsRelative(zone);
247 return ret.empty() ? zone : ret; // HACK FIXME400
248 }
249 void DNSName::makeUsRelative(const DNSName& zone)
250 {
251 if (isPartOf(zone)) {
252 d_storage.erase(d_storage.size()-zone.d_storage.size());
253 d_storage.append(1, (char)0); // put back the trailing 0
254 }
255 else
256 clear();
257 }
258
259 DNSName DNSName::getCommonLabels(const DNSName& other) const
260 {
261 DNSName result;
262
263 const std::vector<std::string> ours = getRawLabels();
264 const std::vector<std::string> others = other.getRawLabels();
265
266 for (size_t pos = 0; ours.size() > pos && others.size() > pos; pos++) {
267 const std::string& ourLabel = ours.at(ours.size() - pos - 1);
268 const std::string& otherLabel = others.at(others.size() - pos - 1);
269
270 if (!pdns_iequals(ourLabel, otherLabel)) {
271 break;
272 }
273
274 result.prependRawLabel(ourLabel);
275 }
276
277 return result;
278 }
279
280 DNSName DNSName::labelReverse() const
281 {
282 DNSName ret;
283
284 if(isRoot())
285 return *this; // we don't create the root automatically below
286
287 if (!empty()) {
288 vector<string> l=getRawLabels();
289 while(!l.empty()) {
290 ret.appendRawLabel(l.back());
291 l.pop_back();
292 }
293 }
294 return ret;
295 }
296
297 void DNSName::appendRawLabel(const std::string& label)
298 {
299 appendRawLabel(label.c_str(), label.length());
300 }
301
302 void DNSName::appendRawLabel(const char* start, unsigned int length)
303 {
304 if(length==0)
305 throw std::range_error("no such thing as an empty label to append");
306 if(length > 63)
307 throw std::range_error("label too long to append");
308 if(d_storage.size() + length > 254) // reserve one byte for the label length
309 throw std::range_error("name too long to append");
310
311 if(d_storage.empty()) {
312 d_storage.append(1, (char)length);
313 }
314 else {
315 *d_storage.rbegin()=(char)length;
316 }
317 d_storage.append(start, length);
318 d_storage.append(1, (char)0);
319 }
320
321 void DNSName::prependRawLabel(const std::string& label)
322 {
323 if(label.empty())
324 throw std::range_error("no such thing as an empty label to prepend");
325 if(label.size() > 63)
326 throw std::range_error("label too long to prepend");
327 if(d_storage.size() + label.size() > 254) // reserve one byte for the label length
328 throw std::range_error("name too long to prepend");
329
330 if(d_storage.empty())
331 d_storage.append(1, (char)0);
332
333 string_t prep(1, (char)label.size());
334 prep.append(label.c_str(), label.size());
335 d_storage = prep+d_storage;
336 }
337
338 bool DNSName::slowCanonCompare(const DNSName& rhs) const
339 {
340 auto ours=getRawLabels(), rhsLabels = rhs.getRawLabels();
341 return std::lexicographical_compare(ours.rbegin(), ours.rend(), rhsLabels.rbegin(), rhsLabels.rend(), CIStringCompare());
342 }
343
344 vector<string> DNSName::getRawLabels() const
345 {
346 vector<string> ret;
347 ret.reserve(countLabels());
348 // 3www4ds9a2nl0
349 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) {
350 ret.push_back({(const char*)p+1, (size_t)*p}); // XXX FIXME
351 }
352 return ret;
353 }
354
355 std::string DNSName::getRawLabel(unsigned int pos) const
356 {
357 unsigned int currentPos = 0;
358 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++) {
359 if (currentPos == pos) {
360 return std::string((const char*)p+1, (size_t)*p);
361 }
362 }
363
364 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");
365 }
366
367 DNSName DNSName::getLastLabel() const
368 {
369 DNSName ret(*this);
370 ret.trimToLabels(1);
371 return ret;
372 }
373
374 bool DNSName::chopOff()
375 {
376 if(d_storage.empty() || d_storage[0]==0)
377 return false;
378 d_storage.erase(0, (unsigned int)d_storage[0]+1);
379 return true;
380 }
381
382 bool DNSName::isWildcard() const
383 {
384 if(d_storage.size() < 2)
385 return false;
386 auto p = d_storage.begin();
387 return (*p == 0x01 && *++p == '*');
388 }
389
390 /*
391 * Returns true if the DNSName is a valid RFC 1123 hostname, this function uses
392 * a regex on the string, so it is probably best not used when speed is essential.
393 */
394 bool DNSName::isHostname() const
395 {
396 static Regex hostNameRegex = Regex("^(([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)\\.)+$");
397 return hostNameRegex.match(this->toString());
398 }
399
400 unsigned int DNSName::countLabels() const
401 {
402 unsigned int count=0;
403 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)
404 ++count;
405 return count;
406 }
407
408 void DNSName::trimToLabels(unsigned int to)
409 {
410 while(countLabels() > to && chopOff())
411 ;
412 }
413
414
415 size_t hash_value(DNSName const& d)
416 {
417 return d.hash();
418 }
419
420 string DNSName::escapeLabel(const std::string& label)
421 {
422 string ret;
423 ret.reserve(label.size()); // saves 15% on bulk .COM load
424 for(uint8_t p : label) {
425 if(p=='.')
426 ret+="\\.";
427 else if(p=='\\')
428 ret+="\\\\";
429 else if(p > 0x20 && p < 0x7f)
430 ret.append(1, (char)p);
431 else {
432 ret+="\\" + (boost::format("%03d") % (unsigned int)p).str();
433 }
434 }
435 return ret;
436 }