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