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