/src/freeradius-server/src/lib/util/dns.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** Functions to manipulate DNS labels |
18 | | * |
19 | | * @file src/lib/util/dns.c |
20 | | * |
21 | | * @copyright 2019 The FreeRADIUS server project |
22 | | * @copyright 2019 Network RADIUS SAS (legal@networkradius.com) |
23 | | */ |
24 | | RCSID("$Id: 8138fc337defc4e396d92e52302e38bc0325225e $") |
25 | | |
26 | | #include <freeradius-devel/util/misc.h> |
27 | | #include <freeradius-devel/util/strerror.h> |
28 | | #include <freeradius-devel/util/value.h> |
29 | | #include <freeradius-devel/util/dns.h> |
30 | | #include <freeradius-devel/util/proto.h> |
31 | | |
32 | 0 | #define MAX_OFFSET (1 << 14) |
33 | | |
34 | | static int dns_label_add(fr_dns_labels_t *lb, uint8_t const *start, uint8_t const *end) |
35 | 0 | { |
36 | 0 | size_t offset, size = end - start; |
37 | 0 | fr_dns_block_t *block; |
38 | | |
39 | | /* |
40 | | * If we don't care about tracking the blocks, then don't |
41 | | * do anything. |
42 | | */ |
43 | 0 | if (!lb) return 0; |
44 | | |
45 | 0 | fr_assert(start >= lb->start); |
46 | 0 | fr_assert(end >= start); |
47 | |
|
48 | 0 | offset = start - lb->start; |
49 | | |
50 | | /* |
51 | | * DNS packets can be up to 64K in size, but the |
52 | | * compressed pointers can only be up to 2^14 in size. |
53 | | * So we just ignore offsets which are greater than 2^14. |
54 | | */ |
55 | 0 | if ((offset + size) >= MAX_OFFSET) return 0; |
56 | | |
57 | | /* |
58 | | * We're not tracking labels, so don't do anything. |
59 | | */ |
60 | 0 | if (lb->max == 1) return 0; |
61 | | |
62 | 0 | FR_PROTO_TRACE("adding label at offset %zu", offset); |
63 | | |
64 | | /* |
65 | | * We add blocks append-only. No adding new blocks in |
66 | | * the middle of a packet. |
67 | | */ |
68 | 0 | block = &lb->blocks[lb->num - 1]; |
69 | 0 | fr_assert(block->start <= offset); |
70 | 0 | fr_assert(offset); |
71 | |
|
72 | 0 | FR_PROTO_TRACE("Last block (%d) is %u..%u", lb->num - 1, block->start, block->end); |
73 | | |
74 | | /* |
75 | | * Fits within an existing block. |
76 | | */ |
77 | 0 | if (block->end == offset) { |
78 | 0 | block->end += size; |
79 | 0 | FR_PROTO_TRACE("Expanding last block (%d) to %u..%u", lb->num - 1, block->start, block->end); |
80 | 0 | return 0; |
81 | 0 | } |
82 | | |
83 | | /* |
84 | | * It's full, die. |
85 | | */ |
86 | 0 | if (lb->num == lb->max) return -1; |
87 | | |
88 | 0 | lb->num++; |
89 | 0 | block++; |
90 | |
|
91 | 0 | block->start = offset; |
92 | 0 | block->end = offset + size; |
93 | 0 | FR_PROTO_TRACE("Appending block (%d) to %u..%u", lb->num - 1, block->start, block->end); |
94 | |
|
95 | 0 | return 0; |
96 | 0 | } |
97 | | |
98 | | static void dns_label_mark(fr_dns_labels_t *lb, uint8_t const *p) |
99 | 42.0k | { |
100 | 42.0k | if (!lb || !lb->mark) return; |
101 | | |
102 | 22.3k | fr_assert(p >= (lb->start + 12)); /* can't point to the packet header */ |
103 | 22.3k | fr_assert(!lb->end || (p < lb->end)); |
104 | | |
105 | 22.3k | lb->mark[p - lb->start] = 1; |
106 | 22.3k | } |
107 | | |
108 | | |
109 | | static bool dns_pointer_valid(fr_dns_labels_t *lb, uint16_t offset) |
110 | 8.21k | { |
111 | 8.21k | int i; |
112 | | |
113 | 8.21k | if (!lb) return true; /* we have no idea, so allow it */ |
114 | | |
115 | 3.50k | if (lb->mark) return (lb->mark[offset] != 0); |
116 | | |
117 | | /* |
118 | | * Brute-force searching. |
119 | | * |
120 | | * @todo - manually walk through the pointers for the block? |
121 | | */ |
122 | 0 | for (i = 0; i < lb->num; i++) { |
123 | 0 | FR_PROTO_TRACE("Checking block %d %u..%u against %u", |
124 | 0 | i, lb->blocks[i].start, lb->blocks[i].end, offset); |
125 | |
|
126 | 0 | if (offset < lb->blocks[i].start) return false; |
127 | | |
128 | 0 | if (offset < lb->blocks[i].end) return true; |
129 | 0 | } |
130 | | |
131 | 0 | return false; |
132 | 0 | } |
133 | | |
134 | | /** Compare two labels in a case-insensitive fashion. |
135 | | * |
136 | | * This function requires that the input is valid, i.e. all |
137 | | * characters are within [-0-9A-Za-z]. If any other input is given, |
138 | | * it will break. |
139 | | */ |
140 | | static bool labelcmp(uint8_t const *a, uint8_t const *b, size_t len) |
141 | 0 | { |
142 | 0 | for (/* nothing */; len > 0; len--) { |
143 | 0 | if (*a == *b) { |
144 | 0 | a++; |
145 | 0 | b++; |
146 | 0 | continue; |
147 | 0 | } |
148 | | |
149 | | /* |
150 | | * If one or the other isn't a letter, we can't |
151 | | * do case insensitive comparisons of them, so |
152 | | * they can't match. |
153 | | */ |
154 | 0 | if (!(((*a >= 'a') && (*a <= 'z')) || ((*a >= 'A') && (*a <= 'Z')))) return false; |
155 | 0 | if (!(((*b >= 'a') && (*b <= 'z')) || ((*b >= 'A') && (*b <= 'Z')))) return false; |
156 | | |
157 | | /* |
158 | | * If they're equal but different case, then the |
159 | | * only bit that's different is 0x20. |
160 | | */ |
161 | 0 | if (((*a)^(*b)) != 0x20) { |
162 | 0 | return false; |
163 | 0 | } |
164 | | |
165 | 0 | a++; |
166 | 0 | b++; |
167 | 0 | } |
168 | | |
169 | 0 | return true; |
170 | 0 | } |
171 | | |
172 | | /** Compress "label" by looking at the label recursively. |
173 | | * |
174 | | * For "ftp.example.com", it searches the input buffer for a matching |
175 | | * "com". It only does string compares if it finds bytes "03 xx xx |
176 | | * xx 00". This means that the scan is quick, because most bytes are |
177 | | * skipped. |
178 | | * |
179 | | * If a matching string is found, the label is updated to replace |
180 | | * "com" with a 2-byte pointer P1. The process then proceeds |
181 | | * recursively, with "exampleP1". |
182 | | * |
183 | | * The input buffer is the scanned again for labels of matching |
184 | | * length (7 here), AND which either end in the value of "P1", or end |
185 | | * *at* P1. If we find a match, we replace "exampleP1" with "P2". |
186 | | * The process then proceeds recursively with "ftpP2". |
187 | | * |
188 | | * Since the algorithm replaces known suffixes with pointers, we |
189 | | * *never* have to compare full names. Instead, we only ever compare |
190 | | * one label to one other label. And then only if the labels have |
191 | | * the same lengths AND the same suffixes (00 byte, or a pointer P). |
192 | | * |
193 | | * As an extra optimization, we track the start of the label where we |
194 | | * found the compressed pointer. e.g. "www.example.com" when |
195 | | * compressing "com". We know that the "com" string CANNOT appear |
196 | | * before this label. Because if it did, then the "www.example.com" |
197 | | * name would have instead been compressed, as in "www.exampleP1". |
198 | | * |
199 | | * This optimization ensures that we scan as little of the buffer as |
200 | | * possible, by moving the search start ahead in the buffer. This |
201 | | * optimization also means that in many cases, the suffix we're |
202 | | * looking for (e.g. "example.com") is in the first label we search. |
203 | | * Which means that we end up ignoring most of the buffer. |
204 | | * |
205 | | * This algorithm is O(N * B), where N is the number of labels in a |
206 | | * name (e.g. 3 for "ftp.example.com"), and "B" is the size of the |
207 | | * buffer. It also does linear scans of the buffer, which are good |
208 | | * for read-ahead. Each input label is compared to labels in the |
209 | | * buffer only when very limited situations apply. And we never |
210 | | * compare the full input name to full names in the buffer. |
211 | | * |
212 | | * In the case where we are adding many names from the same zone to |
213 | | * the input buffer, the input buffer will start with the zone name. |
214 | | * So any searches will match that. The only reason to continue |
215 | | * scanning the buffer is to see if the name prefix already exists. |
216 | | * If we assume that the records do not contain duplicates, then we |
217 | | * can likely skip that scan, too. |
218 | | * |
219 | | * Adding that optimization, however, requires tracking the maximum |
220 | | * size of a name across multiple invocations of the function. For |
221 | | * example, if the maximum length name in the buffer is 3 labels, and |
222 | | * we're adding a 3 label name, then we can stop scanning the buffer |
223 | | * as soon as we compressed the 2 suffix labels. Since we are |
224 | | * guaranteed that there are no duplicates, we are sure that there is |
225 | | * no existing 3-label name which matches a 3-label name in the |
226 | | * buffer. |
227 | | * |
228 | | * Note that this function does NOT follow pointers in the input |
229 | | * buffer! |
230 | | * |
231 | | * |
232 | | * A different and more straightforward approach is to loop over all |
233 | | * labels in the name from longest to shortest, and comparing them to |
234 | | * each name in the buffer in turn. That algorithm ends up being |
235 | | * O(L1 * T * L2), where L1 is the length of the input name, T is the |
236 | | * total number of names in the buffer, and L2 is the average length |
237 | | * of names in the buffer. This algorithm can result in the buffer |
238 | | * being scanned many, many, times. The scan is also done forwards |
239 | | * (due to comparing names one after the other), but also backwards |
240 | | * (due to following pointers). Which makes for poor locality of |
241 | | * reference. |
242 | | * |
243 | | * i.e. that approach *has* to scan the entire input buffer, because |
244 | | * that's where all of the names are. Further, it has to scan it at |
245 | | * least "N" times, because there are N labels in the input name. So |
246 | | * O(N * B) is the *lower* bound for this algorithm. |
247 | | * |
248 | | * It gets worse when the straightforward algorithm does pointer |
249 | | * following instead of pointer comparisons. It ends up scanning |
250 | | * portions of the input buffer many, many, times. i.e. it can |
251 | | * compare an input "com" name to "org" once for every "org" name in |
252 | | * the input buffer. In contrast, because our algorithm does not do |
253 | | * pointer following, it only compares "com" to "org" once. |
254 | | * |
255 | | * @param[in] packet where the packet starts |
256 | | * @param[in] start input buffer holding one or more labels |
257 | | * @param[in] end end of the input buffer |
258 | | * @param[out] new_search Where the parent call to dns_label_compress() |
259 | | * should start searching from, instead of from "start". |
260 | | * @param[in] label label to add to the buffer. |
261 | | * @param[out] label_end updated end of the input label after compression. |
262 | | * @return |
263 | | * - false, we didn't compress the input |
264 | | * - true, we did compress the input. |
265 | | */ |
266 | | static bool dns_label_compress(uint8_t const *packet, uint8_t const *start, uint8_t const *end, uint8_t const **new_search, |
267 | | uint8_t *label, uint8_t **label_end) |
268 | 0 | { |
269 | 0 | uint8_t *next; |
270 | 0 | uint8_t const *q, *ptr, *suffix, *search; |
271 | 0 | uint16_t offset; |
272 | 0 | bool compressed = false; |
273 | | |
274 | | /* |
275 | | * Don't compress "end of label" byte or pointers. |
276 | | */ |
277 | 0 | if (!*label || (*label > 63)) { |
278 | 0 | return false; |
279 | 0 | } |
280 | | |
281 | | /* |
282 | | * Check the next label. Note that this is *after* |
283 | | * "end". It also MUST be a valid, uncompressed label. |
284 | | */ |
285 | 0 | next = label + *label + 1; |
286 | | |
287 | | /* |
288 | | * Note that by design, next > end. We don't care about |
289 | | * the size of the buffer we put "label" into. We only |
290 | | * care that all bytes of "label" are valid, and we don't |
291 | | * access memory after "label". |
292 | | */ |
293 | | |
294 | | /* |
295 | | * On the first call, begin searching from the start of |
296 | | * the buffer. |
297 | | * |
298 | | * For subsequent calls, begin from where we started |
299 | | * searching before. |
300 | | */ |
301 | 0 | if (!new_search) { |
302 | 0 | search = start; |
303 | 0 | } else { |
304 | 0 | search = *new_search; |
305 | 0 | } |
306 | | |
307 | | /* |
308 | | * We're at the last uncompressed label, scan the input |
309 | | * buffer to see if there's a match. |
310 | | * |
311 | | * The scan skips ahead until it find both a label length |
312 | | * that matches, AND "next" label which is 0x00. Only |
313 | | * then does it do the string compare of the label |
314 | | * values. |
315 | | * |
316 | | * We speed this up slightly by tracking the previous |
317 | | * uncompressed pointer. If we do compress the current |
318 | | * label, then we should also tell the caller where the |
319 | | * previous uncompressed label started. That way the |
320 | | * caller can start looking there for the next piece to |
321 | | * compress. There's no need to search from the |
322 | | * beginning of the input buffer, as we're sure that |
323 | | * there is no earlier instance of the suffix we found. |
324 | | * |
325 | | * i.e. as we compress the current name, we start |
326 | | * searching not at the beginning of the input buffer/ |
327 | | * Instead, we start searching at the name which contains |
328 | | * the label that we just compressed. The previous |
329 | | * sarching guarantees that no name *before* that one |
330 | | * will match the suffix we're looking for. So we can |
331 | | * skip all of the previous names in subsequent searches, |
332 | | */ |
333 | 0 | if (*next == 0x00) { |
334 | 0 | q = search; |
335 | 0 | while (q < end) { |
336 | 0 | if (*q == 0x00) { |
337 | 0 | q++; |
338 | | |
339 | | /* |
340 | | * None of the previous names |
341 | | * matched, so we tell the caller |
342 | | * to start searching from the |
343 | | * next name in the buffer. |
344 | | */ |
345 | 0 | search = q; |
346 | 0 | continue; |
347 | 0 | } |
348 | | |
349 | | /* |
350 | | * Our label is a terminal one. Which |
351 | | * can't point to a pointer. |
352 | | */ |
353 | 0 | if (*q > 63) { |
354 | 0 | q += 2; |
355 | | |
356 | | /* |
357 | | * None of the previous |
358 | | * uncompressed names matched, |
359 | | * and this pointer refers to a |
360 | | * compressed name. So it |
361 | | * doesn't match, either. |
362 | | */ |
363 | 0 | search = q; |
364 | 0 | continue; |
365 | 0 | } |
366 | | |
367 | | /* |
368 | | * We now have a label which MIGHT match. |
369 | | * We have to walk down it until it does |
370 | | * match. But we don't update "search" |
371 | | * here, because there may be a suffix |
372 | | * which matches. |
373 | | */ |
374 | 0 | ptr = q + *q + 1; |
375 | 0 | if (ptr > end) return false; |
376 | | |
377 | | /* |
378 | | * Label lengths aren't the same, skip |
379 | | * it. |
380 | | */ |
381 | 0 | if (*q != *label) { |
382 | 0 | q = ptr; |
383 | 0 | continue; |
384 | 0 | } |
385 | | |
386 | | /* |
387 | | * Our input label ends with 0x00. If |
388 | | * this label doesn't end with 0x00, skip |
389 | | * it. |
390 | | */ |
391 | 0 | if (*ptr != 0x00) { |
392 | 0 | q = ptr; |
393 | 0 | continue; |
394 | 0 | } |
395 | | |
396 | | /* |
397 | | * The pointer is too far away. Don't |
398 | | * point to it. This check is mainly for |
399 | | * static analyzers. |
400 | | */ |
401 | 0 | if ((q - packet) > (1 << 14)) return false; |
402 | | |
403 | | /* |
404 | | * Only now do case-insensitive |
405 | | * comparisons. |
406 | | */ |
407 | 0 | if (!labelcmp(q + 1, label + 1, *label)) { |
408 | 0 | q = ptr; |
409 | 0 | continue; |
410 | 0 | } |
411 | | |
412 | | /* |
413 | | * We have a match. Replace the input |
414 | | * label with a compressed pointer. Tell |
415 | | * the caller the start of the found |
416 | | * name, so subsequent searches can start |
417 | | * from there. Then return to the caller |
418 | | * that we managed to compress this |
419 | | * label. |
420 | | */ |
421 | 0 | offset = (q - packet); |
422 | 0 | label[0] = (offset >> 8) | 0xc0; |
423 | 0 | label[1] = offset & 0xff; |
424 | 0 | *label_end = label + 2; |
425 | 0 | if (new_search) *new_search = search; |
426 | 0 | return true; |
427 | 0 | } |
428 | | |
429 | 0 | return false; |
430 | 0 | } |
431 | | |
432 | | /* |
433 | | * The next label is still uncompressed, so we call |
434 | | * ourselves recursively in order to compress it. |
435 | | */ |
436 | 0 | if (*next < 63) { |
437 | 0 | if (!dns_label_compress(packet, start, end, &search, next, label_end)) return false; |
438 | | |
439 | | /* |
440 | | * Else it WAS compressed. |
441 | | */ |
442 | 0 | compressed = true; |
443 | 0 | } |
444 | | |
445 | | /* |
446 | | * The next label wasn't compressed, OR it is invalid, |
447 | | * skip it. This check is here only to shut up the |
448 | | * static analysis tools. |
449 | | */ |
450 | 0 | if (*next < 0xc0) { |
451 | 0 | return compressed; |
452 | 0 | } |
453 | | |
454 | | /* |
455 | | * Remember where our suffix points to. |
456 | | */ |
457 | 0 | suffix = packet + ((next[0] & ~0xc0) << 8) + next[1]; |
458 | | |
459 | | /* |
460 | | * Our label now ends with a compressed pointer. Scan |
461 | | * the input until we find either an uncompressed label |
462 | | * which ends with the same compressed pointer, OR we |
463 | | * find an uncompressed label which ends AT our |
464 | | * compressed pointer. |
465 | | * |
466 | | * Note that we start searching from the beginning of the |
467 | | * label which resulted in us finding the compressed |
468 | | * pointer! |
469 | | * |
470 | | * We're guaranteed that any label BEFORE that one |
471 | | * doesn't end with a matching compressed pointer. |
472 | | */ |
473 | 0 | q = search; |
474 | 0 | while (q < end) { |
475 | 0 | if (*q == 0x00) { |
476 | 0 | q++; |
477 | | |
478 | | /* |
479 | | * None of the previous stuff matched, so |
480 | | * we tell the caller to start searching |
481 | | * from the next name. |
482 | | */ |
483 | 0 | search = q; |
484 | 0 | continue; |
485 | 0 | } |
486 | | |
487 | | /* |
488 | | * Skip compressed pointers. We can't point to |
489 | | * compressed pointers. |
490 | | */ |
491 | 0 | if (*q > 63) { |
492 | 0 | q += 2; |
493 | | |
494 | | /* |
495 | | * None of the previous uncompressed |
496 | | * names matched, and this pointer refers |
497 | | * to a compressed name. So it doesn't |
498 | | * match, either. |
499 | | */ |
500 | 0 | search = q; |
501 | 0 | continue; |
502 | 0 | } |
503 | | |
504 | | /* |
505 | | * We now have an uncompressed label in the input |
506 | | * buffer. Check for a match. |
507 | | */ |
508 | 0 | ptr = q + *q + 1; |
509 | 0 | if (ptr > end) return compressed; |
510 | | |
511 | | /* |
512 | | * Label lengths aren't the same, skip it. |
513 | | */ |
514 | 0 | if (*q != *label) { |
515 | 0 | q = ptr; |
516 | 0 | continue; |
517 | 0 | } |
518 | | |
519 | | /* |
520 | | * If the NEXT label is uncompressed, then skip |
521 | | * it unless it's the suffix we're pointing to. |
522 | | */ |
523 | 0 | if (*ptr < 63) { |
524 | 0 | if (ptr != suffix) { |
525 | 0 | q = ptr; |
526 | 0 | continue; |
527 | 0 | } |
528 | | |
529 | 0 | goto check_label; |
530 | 0 | } |
531 | | |
532 | | /* |
533 | | * The next label is a compressed pointer. If |
534 | | * the compressed pointers are different, then |
535 | | * skip both this label and the compressed |
536 | | * pointer after it. |
537 | | */ |
538 | 0 | if ((ptr[0] != next[0]) || |
539 | 0 | (ptr[1] != next[1])) { |
540 | 0 | q = ptr + 2; |
541 | | |
542 | | /* |
543 | | * None of the previous uncompressed |
544 | | * names matched, and this pointer refers |
545 | | * to a compressed name. So it doesn't |
546 | | * match, either. |
547 | | */ |
548 | 0 | search = q; |
549 | 0 | continue; |
550 | 0 | } |
551 | | |
552 | 0 | check_label: |
553 | | /* |
554 | | * Pointer is too far away. Don't point |
555 | | * to it. |
556 | | */ |
557 | 0 | if ((q - packet) > (1 << 14)) return compressed; |
558 | | |
559 | | /* |
560 | | * Only now do case-insensitive |
561 | | * comparisons. |
562 | | */ |
563 | 0 | if (!labelcmp(q + 1, label + 1, *label)) { |
564 | 0 | q = ptr; |
565 | 0 | continue; |
566 | 0 | } |
567 | | |
568 | | /* |
569 | | * We have a match. Replace the input |
570 | | * label with a compressed pointer. Tell |
571 | | * the caller the start of the found |
572 | | * name, so subsequent searches can start |
573 | | * from there. Then return to the caller |
574 | | * that we managed to compress this |
575 | | * label. |
576 | | */ |
577 | 0 | offset = (q - packet); |
578 | 0 | label[0] = (offset >> 8) | 0xc0; |
579 | 0 | label[1] = offset & 0xff; |
580 | 0 | *label_end = label + 2; |
581 | 0 | if (new_search) *new_search = search; |
582 | 0 | return true; |
583 | 0 | } |
584 | | |
585 | | /* |
586 | | * Who knows what it is, we couldn't compress it. |
587 | | */ |
588 | 0 | return compressed; |
589 | 0 | } |
590 | | |
591 | | |
592 | | /** Encode a single value box of type string, serializing its contents to a dns label |
593 | | * in a dbuff |
594 | | * |
595 | | * @param[in] dbuff Buffer where labels are written |
596 | | * @param[in] compression Whether or not to do DNS label compression. |
597 | | * @param[in] value to encode. |
598 | | * @param[in] lb label tracking data structure. |
599 | | * @return |
600 | | * - >0 the number of bytes written to the dbuff |
601 | | * - 0 could not encode anything, an error has occurred. |
602 | | * - <0 the number of bytes the dbuff should have had, instead of "remaining". |
603 | | */ |
604 | | ssize_t fr_dns_label_from_value_box_dbuff(fr_dbuff_t *dbuff, bool compression, fr_value_box_t const *value, fr_dns_labels_t *lb) |
605 | 0 | { |
606 | 0 | ssize_t slen; |
607 | 0 | size_t need = 0; |
608 | |
|
609 | 0 | slen = fr_dns_label_from_value_box(&need, dbuff->p, fr_dbuff_remaining(dbuff), dbuff->p, compression, value, lb); |
610 | 0 | if (slen < 0) return 0; |
611 | | |
612 | 0 | if (slen == 0) return -need; |
613 | | |
614 | 0 | fr_dbuff_advance(dbuff, (size_t)slen); |
615 | 0 | return slen; |
616 | 0 | } |
617 | | |
618 | | /** Encode a single value box of type string, serializing its contents to a dns label |
619 | | * |
620 | | * This functions takes a large buffer and encodes the label in part |
621 | | * of the buffer. This API is necessary in order to allow DNS label |
622 | | * compression. |
623 | | * |
624 | | * @param[out] need if not NULL, how long "buf_len" should be to |
625 | | * serialize the rest of the data. |
626 | | * Note: Only variable length types will be partially |
627 | | * encoded. Fixed length types will not be partially encoded. |
628 | | * @param[out] buf Buffer where labels are stored |
629 | | * @param[in] buf_len The length of the output buffer |
630 | | * @param[out] where Where to write this label |
631 | | * @param[in] compression Whether or not to do DNS label compression. |
632 | | * @param[in] value to encode. |
633 | | * @param[in] lb label tracking data structure |
634 | | * @return |
635 | | * - 0 no bytes were written, see need value to determine |
636 | | * - >0 the number of bytes written to "where", NOT "buf + where + outlen" |
637 | | * - <0 on error. |
638 | | */ |
639 | | ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buf_len, uint8_t *where, bool compression, |
640 | | fr_value_box_t const *value, fr_dns_labels_t *lb) |
641 | 0 | { |
642 | 0 | uint8_t *label; |
643 | 0 | uint8_t const *end = buf + buf_len; |
644 | 0 | uint8_t const *q, *strend, *last; |
645 | 0 | uint8_t *data; |
646 | 0 | bool underscore = true; |
647 | |
|
648 | 0 | if (!buf || !buf_len || !where || !value) { |
649 | 0 | fr_strerror_const("Invalid input"); |
650 | 0 | return -1; |
651 | 0 | } |
652 | | |
653 | | /* |
654 | | * Don't allow stupidities |
655 | | */ |
656 | 0 | if (!((where >= buf) && (where < (buf + buf_len)))) { |
657 | 0 | fr_strerror_const("Label to write is outside of buffer"); |
658 | 0 | return -1; |
659 | 0 | } |
660 | | |
661 | | /* |
662 | | * We can only encode strings. |
663 | | */ |
664 | 0 | if (value->type != FR_TYPE_STRING) { |
665 | 0 | fr_strerror_const("Asked to encode non-string type"); |
666 | 0 | return -1; |
667 | 0 | } |
668 | | |
669 | | /* |
670 | | * '.' or empty string is special, and is encoded as a |
671 | | * plain zero byte. |
672 | | * |
673 | | * Since "where < end", we can always write 1 byte to it. |
674 | | */ |
675 | 0 | if ((value->vb_length == 0) || |
676 | 0 | ((value->vb_length == 1) && (value->vb_strvalue[0] == '.'))) { |
677 | 0 | *where = 0x00; |
678 | 0 | return 1; |
679 | 0 | } |
680 | | |
681 | | /* |
682 | | * Sanity check the name before writing anything to the |
683 | | * buffer. |
684 | | * |
685 | | * Only encode [-0-9a-zA-Z]. Anything else is forbidden. |
686 | | * Dots at the start are forbidden. Double dots are |
687 | | * forbidden. |
688 | | */ |
689 | 0 | q = (uint8_t const *) value->vb_strvalue; |
690 | 0 | strend = q + value->vb_length; |
691 | 0 | last = q; |
692 | |
|
693 | 0 | if (*q == '.') { |
694 | 0 | fr_strerror_const("Empty labels are invalid"); |
695 | 0 | return -1; |
696 | 0 | } |
697 | | |
698 | | /* |
699 | | * Convert it piece by piece. |
700 | | */ |
701 | 0 | while (q < strend) { |
702 | | /* |
703 | | * Allow underscore at the start of a label. |
704 | | */ |
705 | 0 | if (underscore) { |
706 | 0 | underscore = false; |
707 | |
|
708 | 0 | if (*q == '_') goto next; |
709 | 0 | } |
710 | | |
711 | 0 | if (*q == '.') { |
712 | | /* |
713 | | * Don't count final dot as an |
714 | | * intermediate dot, and don't bother |
715 | | * encoding it. |
716 | | */ |
717 | 0 | if ((q + 1) == strend) { |
718 | 0 | strend--; |
719 | 0 | break; |
720 | 0 | } |
721 | | |
722 | 0 | if (q[1] == '.') { |
723 | 0 | fr_strerror_const("Double dots '..' are forbidden"); |
724 | 0 | return -1; |
725 | 0 | } |
726 | 0 | last = q; |
727 | | |
728 | | /* |
729 | | * We had a dot, allow underscore as the |
730 | | * first character of the next label. |
731 | | */ |
732 | 0 | underscore = true; |
733 | |
|
734 | 0 | } else if (!((*q == '-') || ((*q >= '0') && (*q <= '9')) || |
735 | 0 | ((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z')))) { |
736 | 0 | fr_strerror_printf("Invalid character 0x%02x in label", *q); |
737 | 0 | return -1; |
738 | 0 | } |
739 | | |
740 | 0 | next: |
741 | 0 | q++; |
742 | |
|
743 | 0 | if ((q - last) > 63) { |
744 | 0 | fr_strerror_const("Label is larger than 63 characters"); |
745 | 0 | return -1; |
746 | 0 | } |
747 | 0 | } |
748 | | |
749 | 0 | q = (uint8_t const *) value->vb_strvalue; |
750 | | |
751 | | /* |
752 | | * For now, just encode the value as-is. We do |
753 | | * compression as a second step. |
754 | | * |
755 | | * We need a minimum length of string + beginning length |
756 | | * + trailing zero. Intermediate '.' are converted to |
757 | | * length bytes. |
758 | | */ |
759 | 0 | if ((where + (strend - q) + 2) > end) { |
760 | 0 | if (need) *need = (where + (strend - q) + 2) - buf; |
761 | 0 | return 0; |
762 | 0 | } |
763 | | |
764 | 0 | label = where; |
765 | 0 | *label = 0; |
766 | 0 | data = label + 1; |
767 | | |
768 | | /* |
769 | | * @todo - encode into a local buffer, and then try to |
770 | | * compress that into the output buffer. This means that |
771 | | * the output buffer can be a little bit smaller. |
772 | | */ |
773 | 0 | while (q < strend) { |
774 | 0 | fr_assert(data < end); |
775 | 0 | fr_assert((data - where) < 255); |
776 | | |
777 | | /* |
778 | | * '.' is a label delimiter. |
779 | | * |
780 | | * We've already checked above for '.' at the |
781 | | * start, for double dots, and have already |
782 | | * suppressed '.' at the end of the string. |
783 | | * |
784 | | * Start a new label. |
785 | | */ |
786 | 0 | if (*q == '.') { |
787 | 0 | label = data; |
788 | 0 | *label = 0; |
789 | 0 | data = label + 1; |
790 | |
|
791 | 0 | q++; |
792 | 0 | continue; |
793 | 0 | } |
794 | | |
795 | 0 | *(data++) = *(q++); |
796 | 0 | (*label)++; |
797 | 0 | fr_assert(*label <= 63); |
798 | 0 | } |
799 | |
|
800 | 0 | *(data++) = 0; /* end of label */ |
801 | | |
802 | | /* |
803 | | * If we're compressing it, and we have data to compress, |
804 | | * then do it. |
805 | | */ |
806 | 0 | if (compression && ((data - where) > 2)) { |
807 | 0 | if (lb) { |
808 | 0 | int i; |
809 | | |
810 | | /* |
811 | | * Loop over the parts of the packet which have DNS labels. |
812 | | * |
813 | | * Note that the dns_label_compress() function does NOT follow pointers in the |
814 | | * start/end block which it's searching! It just tries to compress the *input*, |
815 | | * and assumes that the input is compressed last label to first label. |
816 | | * |
817 | | * In addition, dns_label_compress() tracks where in the block it started |
818 | | * searching. So it only scans the block once, even if we pass a NULL search |
819 | | * parameter to it. |
820 | | * |
821 | | * We could start compression from the *last* block. When we add |
822 | | * "www.example.com" and then "ftp.example.com", we could point "ftp" to the |
823 | | * "example.com" portion. which is already in the packet. However, doing that |
824 | | * would require that dns_label_compress() follows pointers in the block it's |
825 | | * searching. Which would greatly increase the complexity of the algorithm. |
826 | | * |
827 | | * |
828 | | * We could still optimize this algorithm a bit, by tracking which parts of the |
829 | | * buffer have DNS names of label length 1, 2, etc. Doing that would mean more |
830 | | * complex data structures, but fewer passes over the packet. |
831 | | */ |
832 | 0 | for (i = 0; i < lb->num; i++) { |
833 | 0 | bool compressed; |
834 | |
|
835 | 0 | FR_PROTO_TRACE("Trying to compress %s in block %d of %u..%u", |
836 | 0 | value->vb_strvalue, i, |
837 | 0 | lb->blocks[i].start, lb->blocks[i].end); |
838 | |
|
839 | 0 | compressed = dns_label_compress(lb->start, lb->start + lb->blocks[i].start, |
840 | 0 | lb->start + lb->blocks[i].end, |
841 | 0 | NULL, where, &data); |
842 | 0 | if (compressed) { |
843 | 0 | FR_PROTO_TRACE("Compressed label in block %d", i); |
844 | 0 | if (*(where + *where + 1) >= 0xc0) { |
845 | 0 | FR_PROTO_TRACE("Next label is compressed, stopping"); |
846 | 0 | } |
847 | 0 | } |
848 | 0 | } |
849 | |
|
850 | 0 | dns_label_add(lb, where, data); |
851 | |
|
852 | 0 | } else if (buf != where) { |
853 | 0 | if (dns_label_compress(buf, buf, where, NULL, where, &data)) { |
854 | 0 | FR_PROTO_TRACE("Compressed single label %s to %zu bytes", |
855 | 0 | value->vb_strvalue, (size_t) (data - where)); |
856 | 0 | } else { |
857 | 0 | FR_PROTO_TRACE("Did not compress single label"); |
858 | 0 | } |
859 | 0 | } |
860 | 0 | } else { |
861 | 0 | FR_PROTO_TRACE("Not compressing label"); |
862 | 0 | } |
863 | |
|
864 | 0 | fr_assert(data > where); |
865 | 0 | return data - where; |
866 | 0 | } |
867 | | |
868 | | /** Get the *uncompressed* length of a DNS label in a network buffer. |
869 | | * |
870 | | * i.e. how bytes are required to store the uncompressed version of |
871 | | * the label. |
872 | | * |
873 | | * Note that a bare 0x00 byte has length 1, to account for '.' |
874 | | * |
875 | | * @param[in] packet where the packet starts |
876 | | * @param[in] buf buffer holding one or more DNS labels |
877 | | * @param[in] buf_len total length of the buffer |
878 | | * @param[in,out] next the DNS label to check, updated to point to the next label |
879 | | * @param[in] lb label tracking data structure |
880 | | * @return |
881 | | * - <=0 on error, offset from buf where the invalid label is located. |
882 | | * - > 0 decoded size of this particular DNS label |
883 | | */ |
884 | | ssize_t fr_dns_label_uncompressed_length(uint8_t const *packet, uint8_t const *buf, size_t buf_len, uint8_t const **next, fr_dns_labels_t *lb) |
885 | 77.2k | { |
886 | 77.2k | uint8_t const *p, *q, *end, *label_end; |
887 | 77.2k | uint8_t const *current, *start; |
888 | 77.2k | size_t length; |
889 | 77.2k | bool at_first_label, already_set_next; |
890 | | |
891 | 77.2k | if (!packet || !buf || (buf_len == 0) || !next) { |
892 | 1.31k | fr_strerror_printf("Invalid argument"); |
893 | 1.31k | return 0; |
894 | 1.31k | } |
895 | | |
896 | 75.9k | start = *next; |
897 | | |
898 | | /* |
899 | | * Don't allow stupidities |
900 | | */ |
901 | 75.9k | if (!((start >= packet) && (start < (buf + buf_len)))) { |
902 | 0 | fr_strerror_printf("Label is not within the buffer"); |
903 | 0 | return 0; |
904 | 0 | } |
905 | | |
906 | 75.9k | end = buf + buf_len; |
907 | 75.9k | p = current = start; |
908 | 75.9k | length = 0; |
909 | 75.9k | at_first_label = true; |
910 | 75.9k | already_set_next = false; |
911 | | |
912 | | /* |
913 | | * We silently accept labels *without* a trailing 0x00, |
914 | | * so long as they end at the end of the input buffer. |
915 | | */ |
916 | 123k | while (p < end) { |
917 | | /* |
918 | | * End of label byte. Skip it. |
919 | | * |
920 | | * Empty labels are length 1, to account for the |
921 | | * '.'. The caller has to take care of this |
922 | | * manually. |
923 | | */ |
924 | 122k | if (*p == 0x00) { |
925 | 66.6k | p++; |
926 | 66.6k | if (at_first_label) length++; |
927 | | |
928 | | /* |
929 | | * We're still processing the first |
930 | | * label, tell the caller where the next |
931 | | * one is located. |
932 | | */ |
933 | 66.6k | if (current == start) { |
934 | 62.5k | *next = p; |
935 | 62.5k | already_set_next = true; |
936 | 62.5k | } |
937 | | |
938 | 66.6k | break; |
939 | 66.6k | } |
940 | | |
941 | | /* |
942 | | * If there's only one byte in the packet, then |
943 | | * it MUST be 0x00. If it's not, then the label |
944 | | * overflows the buffer. |
945 | | */ |
946 | 55.4k | if ((p + 1) >= end) goto overflow; |
947 | | |
948 | | /* |
949 | | * 0b10 and 0b10 are forbidden |
950 | | */ |
951 | 53.3k | if ((*p > 63) && (*p < 0xc0)) { |
952 | 814 | fr_strerror_const("Data with invalid high bits"); |
953 | 814 | return -(p - packet); |
954 | 814 | } |
955 | | |
956 | | /* |
957 | | * Maybe it's a compressed pointer. |
958 | | */ |
959 | 52.5k | if (*p > 63) { |
960 | 9.62k | uint16_t offset; |
961 | | |
962 | 9.62k | if ((p + 2) > end) { |
963 | 2.93k | overflow: |
964 | 2.93k | fr_strerror_const("Label overflows buffer"); |
965 | 2.93k | return -(p - packet); |
966 | 0 | } |
967 | | |
968 | 9.62k | offset = p[1]; |
969 | 9.62k | offset += ((*p & ~0xc0) << 8); |
970 | | |
971 | | /* |
972 | | * Forward references are forbidden, |
973 | | * including self-references. |
974 | | * |
975 | | * This requirement follows RFC 1035 |
976 | | * Section 4.1.4, which says: |
977 | | * |
978 | | * ... an entire domain name or a list of |
979 | | * labels at the end of a domain name is |
980 | | * replaced with a pointer to a prior |
981 | | * occurrence of the same name. |
982 | | * ... |
983 | | * |
984 | | * Note the key word PRIOR. If we |
985 | | * enforce that the pointer is backwards, |
986 | | * and do various other enforcements, |
987 | | * then it is very difficult for |
988 | | * attackers to create malicious DNS |
989 | | * packets which will cause the decoder |
990 | | * to do bad things. |
991 | | */ |
992 | 9.62k | if (offset >= (p - packet)) { |
993 | 1.05k | fr_strerror_printf("Pointer %04x at offset %04x is an invalid forward reference", |
994 | 1.05k | offset, (unsigned int) (p - packet)); |
995 | 1.05k | return -(p - packet); |
996 | 1.05k | } |
997 | | |
998 | 8.56k | q = packet + offset; |
999 | | |
1000 | | /* |
1001 | | * As an additional sanity check, the |
1002 | | * pointer MUST NOT point to something |
1003 | | * within the label we're parsing. If |
1004 | | * that happens, we have a loop. |
1005 | | * |
1006 | | * i.e. the pointer must point backwards |
1007 | | * to *before* our current label. When |
1008 | | * that limitation is enforced, pointer |
1009 | | * loops are impossible. |
1010 | | */ |
1011 | 8.56k | if (q >= current) { |
1012 | 357 | fr_strerror_printf("Pointer %04x at offset %04x creates a loop within a label", |
1013 | 357 | offset, (unsigned int) (p - packet)); |
1014 | 357 | return -(p - packet); |
1015 | 357 | } |
1016 | | |
1017 | | /* |
1018 | | * If we're tracking which labels are |
1019 | | * valid, then check the pointer, too. |
1020 | | */ |
1021 | 8.21k | if (!dns_pointer_valid(lb, offset)) { |
1022 | 443 | fr_strerror_printf("Pointer %04x at offset %04x does not point to a DNS label", |
1023 | 443 | offset, (unsigned int) (p - packet)); |
1024 | 443 | return -(p - packet); |
1025 | 443 | } |
1026 | | |
1027 | | /* |
1028 | | * The pointer MUST point to a valid |
1029 | | * length field, and not to another |
1030 | | * pointer. |
1031 | | */ |
1032 | 7.76k | if (*q > 63) { |
1033 | 158 | fr_strerror_printf("Pointer %04x at offset %04x does not point to the start of a label", |
1034 | 158 | offset, (unsigned int) (p - packet)); |
1035 | 158 | return -(p - packet); |
1036 | 158 | } |
1037 | | |
1038 | | /* |
1039 | | * The pointer MUST NOT point to an end of label field. |
1040 | | */ |
1041 | 7.60k | if (!*q) { |
1042 | 158 | fr_strerror_printf("Pointer %04x at offset %04x refers to an invalid field", offset, |
1043 | 158 | (unsigned int) (p - packet)); |
1044 | 158 | return -(p - packet); |
1045 | 158 | } |
1046 | | |
1047 | | /* |
1048 | | * If we're jumping away from the label |
1049 | | * we started with, tell the caller where |
1050 | | * the next label is in the network |
1051 | | * buffer. |
1052 | | */ |
1053 | 7.45k | if (current == start) { |
1054 | 5.08k | *next = p + 2; |
1055 | 5.08k | already_set_next = true; |
1056 | 5.08k | } |
1057 | | |
1058 | 7.45k | p = current = q; |
1059 | 7.45k | continue; |
1060 | 7.60k | } |
1061 | | |
1062 | | /* |
1063 | | * Else it's an uncompressed label |
1064 | | */ |
1065 | 42.9k | if ((p + *p + 1) > end) goto overflow; |
1066 | | |
1067 | | /* |
1068 | | * It's a valid label. Mark it as such. |
1069 | | */ |
1070 | 42.0k | dns_label_mark(lb, p); |
1071 | | |
1072 | | /* |
1073 | | * Account for the '.' on every label after the |
1074 | | * first one. |
1075 | | */ |
1076 | 42.0k | if (!at_first_label) length++; |
1077 | 42.0k | at_first_label = false; |
1078 | 42.0k | length += *p; |
1079 | | |
1080 | | /* |
1081 | | * DNS names can be no more than 255 octets. |
1082 | | */ |
1083 | 42.0k | if (length > 255) { |
1084 | 80 | fr_strerror_const("Total length of labels is > 255"); |
1085 | 80 | return -(p - packet); |
1086 | 80 | } |
1087 | | |
1088 | 41.9k | q = p + 1; |
1089 | 41.9k | label_end = q + *p; |
1090 | | |
1091 | | /* |
1092 | | * Allow for underscore at the beginning of a |
1093 | | * label. |
1094 | | */ |
1095 | 41.9k | if (*q == '_') q++; |
1096 | | |
1097 | | /* |
1098 | | * Verify that the contents of the label are OK. |
1099 | | */ |
1100 | 92.2k | while (q < label_end) { |
1101 | 52.1k | if (!((*q == '-') || ((*q >= '0') && (*q <= '9')) || |
1102 | 29.5k | ((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z')))) { |
1103 | 1.78k | fr_strerror_printf("Invalid character 0x%02x in label", *q); |
1104 | 1.78k | return -(q - packet); |
1105 | 1.78k | } |
1106 | | |
1107 | 50.3k | q++; |
1108 | 50.3k | } |
1109 | | |
1110 | 40.1k | p += *p + 1; |
1111 | 40.1k | } |
1112 | | |
1113 | | /* |
1114 | | * Return the length of this label. |
1115 | | */ |
1116 | 68.1k | if (!already_set_next) *next = p; /* should be <='end' */ |
1117 | | |
1118 | | /* |
1119 | | * Add the label, only if we're not using the markup field. |
1120 | | */ |
1121 | 68.1k | if (lb && !lb->mark) (void) dns_label_add(lb, start, *next); |
1122 | | |
1123 | 68.1k | return length; |
1124 | 75.9k | } |
1125 | | |
1126 | | /** Verify that a network buffer contains valid DNS labels. |
1127 | | * |
1128 | | * @param[in] packet where the packet starts |
1129 | | * @param[in] buf buffer holding one or more DNS labels |
1130 | | * @param[in] buf_len total length of the buffer |
1131 | | * @param[in] start where to start looking |
1132 | | * @param[in] lb label tracking data structure |
1133 | | * @return |
1134 | | * - <=0 on error, where in the buffer the invalid label is located. |
1135 | | * - > 0 total size of the encoded label(s). Will be <= buf_len |
1136 | | */ |
1137 | | ssize_t fr_dns_labels_network_verify(uint8_t const *packet, uint8_t const *buf, size_t buf_len, uint8_t const *start, fr_dns_labels_t *lb) |
1138 | 6.77k | { |
1139 | 6.77k | ssize_t slen; |
1140 | 6.77k | uint8_t const *label = start; |
1141 | 6.77k | uint8_t const *end = buf + buf_len; |
1142 | | |
1143 | 9.59k | while (label < end) { |
1144 | 8.08k | if (*label == 0x00) { |
1145 | 1.64k | label++; |
1146 | 1.64k | break; |
1147 | 1.64k | } |
1148 | | |
1149 | 6.44k | slen = fr_dns_label_uncompressed_length(packet, buf, buf_len, &label, lb); |
1150 | 6.44k | if (slen <= 0) return slen; /* already is offset from 'buf' and not 'label' */ |
1151 | 6.44k | } |
1152 | | |
1153 | 3.14k | return label - buf; |
1154 | 6.77k | } |
1155 | | |
1156 | | static ssize_t dns_label_decode(uint8_t const *packet, uint8_t const *end, uint8_t const **start, uint8_t const **next) |
1157 | 11.8k | { |
1158 | 11.8k | uint8_t const *p, *q; |
1159 | | |
1160 | 11.8k | p = *start; |
1161 | | |
1162 | 11.8k | if (end == packet) return -1; |
1163 | | |
1164 | 11.8k | if (*p == 0x00) { |
1165 | 0 | *next = p + 1; |
1166 | 0 | return 0; |
1167 | 0 | } |
1168 | | |
1169 | | /* |
1170 | | * Pointer, which points somewhere in the packet. |
1171 | | */ |
1172 | 11.8k | if (*p >= 0xc0) { |
1173 | 2.59k | uint16_t offset; |
1174 | | |
1175 | 2.59k | if ((end - packet) < 2) { |
1176 | 0 | return -(p - packet); |
1177 | 0 | } |
1178 | | |
1179 | 2.59k | offset = p[1]; |
1180 | 2.59k | offset += ((*p & ~0xc0) << 8); |
1181 | | |
1182 | 2.59k | q = packet + offset; |
1183 | 2.59k | if (q >= p) { |
1184 | 0 | return -(p - packet); |
1185 | 0 | } |
1186 | 2.59k | p = q; |
1187 | 2.59k | } |
1188 | | |
1189 | | /* |
1190 | | * 0b10 and 0b10 are forbidden, and pointers can't point to other pointers. |
1191 | | */ |
1192 | 11.8k | if (*p > 63) return -(p - packet); |
1193 | | |
1194 | 11.8k | if ((p + *p + 1) > end) { |
1195 | 0 | return -(p - packet); |
1196 | 0 | } |
1197 | | |
1198 | | /* |
1199 | | * Tell the caller where the actual label is located. |
1200 | | */ |
1201 | 11.8k | *start = p; |
1202 | 11.8k | *next = p + *p + 1; |
1203 | 11.8k | return *p; |
1204 | 11.8k | } |
1205 | | |
1206 | | |
1207 | | /** Decode a #fr_value_box_t from one DNS label |
1208 | | * |
1209 | | * The output type is always FR_TYPE_STRING |
1210 | | * |
1211 | | * Note that the caller MUST call fr_dns_labels_network_verify(src, len, start) |
1212 | | * before calling this function. Otherwise bad things will happen. |
1213 | | * |
1214 | | * @param[in] ctx Where to allocate any talloc buffers required. |
1215 | | * @param[out] dst value_box to write the result to. |
1216 | | * @param[in] src Start of the buffer containing DNS labels |
1217 | | * @param[in] len Length of the buffer to decode |
1218 | | * @param[in] label This particular label |
1219 | | * @param[in] tainted Whether the value came from a trusted source. |
1220 | | * @param[in] lb label tracking data structure |
1221 | | * @return |
1222 | | * - >= 0 The number of network bytes consumed. |
1223 | | * - <0 on error. |
1224 | | */ |
1225 | | ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst, |
1226 | | uint8_t const *src, size_t len, uint8_t const *label, |
1227 | | bool tainted, fr_dns_labels_t *lb) |
1228 | 34.2k | { |
1229 | 34.2k | ssize_t slen; |
1230 | 34.2k | uint8_t const *after = label; |
1231 | 34.2k | uint8_t const *current, *next = NULL; |
1232 | 34.2k | uint8_t const *packet = src; |
1233 | 34.2k | uint8_t const *end = packet + len; |
1234 | 34.2k | uint8_t *p; |
1235 | 34.2k | char *q; |
1236 | | |
1237 | 34.2k | if (!len) return -1; |
1238 | | |
1239 | | /* |
1240 | | * The label must be within the current buffer we're |
1241 | | * passed. |
1242 | | */ |
1243 | 34.2k | if ((label < src) || (label >= end)) return -1; |
1244 | | |
1245 | | /* |
1246 | | * The actual packet might start earlier than the buffer, |
1247 | | * so reset it if necessary. |
1248 | | */ |
1249 | 34.2k | if (lb) packet = lb->start; |
1250 | | |
1251 | | /* |
1252 | | * Get the uncompressed length of the label, and the |
1253 | | * label after this one. |
1254 | | */ |
1255 | 34.2k | slen = fr_dns_label_uncompressed_length(packet, src, len, &after, lb); |
1256 | 34.2k | if (slen <= 0) { |
1257 | 0 | FR_PROTO_TRACE("dns_label_to_value_box - Failed getting length"); |
1258 | 0 | return slen; |
1259 | 0 | } |
1260 | | |
1261 | 34.2k | fr_value_box_init_null(dst); |
1262 | | |
1263 | | /* |
1264 | | * An empty label is a 0x00 byte. Just create an empty |
1265 | | * string. |
1266 | | */ |
1267 | 34.2k | if (slen == 1) { |
1268 | 31.8k | if (fr_value_box_bstr_alloc(ctx, &q, dst, NULL, 1, tainted) < 0) return -1; |
1269 | 31.8k | q[0] = '.'; |
1270 | 31.8k | return after - label; |
1271 | 31.8k | } |
1272 | | |
1273 | | /* |
1274 | | * Allocate the string and set up the value_box |
1275 | | */ |
1276 | 2.43k | if (fr_value_box_bstr_alloc(ctx, &q, dst, NULL, slen, tainted) < 0) return -1; |
1277 | | |
1278 | 2.43k | current = label; |
1279 | 2.43k | p = (uint8_t *) q; |
1280 | 2.43k | q += slen; |
1281 | | |
1282 | 14.3k | while (current && (current < after) && (*current != 0x00)) { |
1283 | | /* |
1284 | | * Get how many bytes this label has, and where |
1285 | | * we will go to obtain the next label. |
1286 | | */ |
1287 | 11.8k | slen = dns_label_decode(packet, end, ¤t, &next); |
1288 | 11.8k | if (slen < 0) return slen; |
1289 | | |
1290 | | /* |
1291 | | * As a sanity check, ensure we don't have a |
1292 | | * buffer overflow. |
1293 | | */ |
1294 | 11.8k | if ((p + slen) > (uint8_t *) q) { |
1295 | 0 | FR_PROTO_TRACE("dns_label_to_value_box - length %zd Failed at %d", slen, __LINE__); |
1296 | |
|
1297 | 0 | fail: |
1298 | 0 | fr_value_box_clear(dst); |
1299 | 0 | return -1; |
1300 | 0 | } |
1301 | | |
1302 | | /* |
1303 | | * Add '.' before the label, but only for the |
1304 | | * second and subsequent labels. |
1305 | | */ |
1306 | 11.8k | if (p != (uint8_t const *) dst->vb_strvalue) { |
1307 | 9.44k | *(p++) = '.'; |
1308 | 9.44k | } |
1309 | | |
1310 | | /* |
1311 | | * Copy the raw bytes from the network. |
1312 | | */ |
1313 | 11.8k | memcpy(p, current + 1, slen); |
1314 | | |
1315 | | /* |
1316 | | * Go ahead in the output string, and go to the |
1317 | | * next label for decoding. |
1318 | | */ |
1319 | 11.8k | p += slen; |
1320 | 11.8k | current = next; |
1321 | 11.8k | } |
1322 | | |
1323 | | /* |
1324 | | * As a last sanity check, ensure that we've filled the |
1325 | | * buffer exactly. |
1326 | | */ |
1327 | 2.43k | if (p != (uint8_t *) q) { |
1328 | 0 | FR_PROTO_TRACE("dns_label_to_value_box - Failed at %d", __LINE__); |
1329 | 0 | goto fail; |
1330 | 0 | } |
1331 | | |
1332 | 2.43k | *p = '\0'; |
1333 | | |
1334 | | /* |
1335 | | * Return the number of network bytes used to parse this |
1336 | | * part of the label. |
1337 | | */ |
1338 | 2.43k | return after - label; |
1339 | 2.43k | } |