]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsname.cc
Merge pull request #5212 from tfarina/dns2-tolower
[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::labelReverse() const
257 {
258 DNSName ret;
259
260 if(isRoot())
261 return *this; // we don't create the root automatically below
262
263 if (!empty()) {
264 vector<string> l=getRawLabels();
265 while(!l.empty()) {
266 ret.appendRawLabel(l.back());
267 l.pop_back();
268 }
269 }
270 return ret;
271 }
272
273 void DNSName::appendRawLabel(const std::string& label)
274 {
275 appendRawLabel(label.c_str(), label.length());
276 }
277
278 void DNSName::appendRawLabel(const char* start, unsigned int length)
279 {
280 if(length==0)
281 throw std::range_error("no such thing as an empty label to append");
282 if(length > 63)
283 throw std::range_error("label too long to append");
284 if(d_storage.size() + length > 254) // reserve one byte for the label length
285 throw std::range_error("name too long to append");
286
287 if(d_storage.empty()) {
288 d_storage.append(1, (char)length);
289 }
290 else {
291 *d_storage.rbegin()=(char)length;
292 }
293 d_storage.append(start, length);
294 d_storage.append(1, (char)0);
295 }
296
297 void DNSName::prependRawLabel(const std::string& label)
298 {
299 if(label.empty())
300 throw std::range_error("no such thing as an empty label to prepend");
301 if(label.size() > 63)
302 throw std::range_error("label too long to prepend");
303 if(d_storage.size() + label.size() > 254) // reserve one byte for the label length
304 throw std::range_error("name too long to prepend");
305
306 if(d_storage.empty())
307 d_storage.append(1, (char)0);
308
309 string_t prep(1, (char)label.size());
310 prep.append(label.c_str(), label.size());
311 d_storage = prep+d_storage;
312 }
313
314 bool DNSName::slowCanonCompare(const DNSName& rhs) const
315 {
316 auto ours=getRawLabels(), rhsLabels = rhs.getRawLabels();
317 return std::lexicographical_compare(ours.rbegin(), ours.rend(), rhsLabels.rbegin(), rhsLabels.rend(), CIStringCompare());
318 }
319
320 vector<string> DNSName::getRawLabels() const
321 {
322 vector<string> ret;
323 ret.reserve(countLabels());
324 // 3www4ds9a2nl0
325 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) {
326 ret.push_back({(const char*)p+1, (size_t)*p}); // XXX FIXME
327 }
328 return ret;
329 }
330
331 std::string DNSName::getRawLabel(unsigned int pos) const
332 {
333 unsigned int currentPos = 0;
334 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++) {
335 if (currentPos == pos) {
336 return std::string((const char*)p+1, (size_t)*p);
337 }
338 }
339
340 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");
341 }
342
343 bool DNSName::chopOff()
344 {
345 if(d_storage.empty() || d_storage[0]==0)
346 return false;
347 d_storage.erase(0, (unsigned int)d_storage[0]+1);
348 return true;
349 }
350
351 bool DNSName::isWildcard() const
352 {
353 if(d_storage.size() < 2)
354 return false;
355 auto p = d_storage.begin();
356 return (*p == 0x01 && *++p == '*');
357 }
358
359 /*
360 * Returns true if the DNSName is a valid RFC 1123 hostname, this function uses
361 * a regex on the string, so it is probably best not used when speed is essential.
362 */
363 bool DNSName::isHostname() const
364 {
365 static Regex hostNameRegex = Regex("^(([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)\\.)+$");
366 return hostNameRegex.match(this->toString());
367 }
368
369 unsigned int DNSName::countLabels() const
370 {
371 unsigned int count=0;
372 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)
373 ++count;
374 return count;
375 }
376
377 void DNSName::trimToLabels(unsigned int to)
378 {
379 while(countLabels() > to && chopOff())
380 ;
381 }
382
383
384 size_t hash_value(DNSName const& d)
385 {
386 return d.hash();
387 }
388
389 string DNSName::escapeLabel(const std::string& label)
390 {
391 string ret;
392 ret.reserve(label.size()); // saves 15% on bulk .COM load
393 for(uint8_t p : label) {
394 if(p=='.')
395 ret+="\\.";
396 else if(p=='\\')
397 ret+="\\\\";
398 else if(p > 0x21 && p < 0x7e)
399 ret.append(1, (char)p);
400 else {
401 ret+="\\" + (boost::format("%03d") % (unsigned int)p).str();
402 }
403 }
404 return ret;
405 }