The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
dns.c
Go to the documentation of this file.
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 */
24RCSID("$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#define MAX_OFFSET (1 << 14)
33
34static int dns_label_add(fr_dns_labels_t *lb, uint8_t const *start, uint8_t const *end)
35{
36 size_t offset, size = end - start;
37 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 if (!lb) return 0;
44
45 fr_assert(start >= lb->start);
46 fr_assert(end >= start);
47
48 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 if ((offset + size) >= MAX_OFFSET) return 0;
56
57 /*
58 * We're not tracking labels, so don't do anything.
59 */
60 if (lb->max == 1) return 0;
61
62 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 block = &lb->blocks[lb->num - 1];
69 fr_assert(block->start <= offset);
70 fr_assert(offset);
71
72 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 if (block->end == offset) {
78 block->end += size;
79 FR_PROTO_TRACE("Expanding last block (%d) to %u..%u", lb->num - 1, block->start, block->end);
80 return 0;
81 }
82
83 /*
84 * It's full, die.
85 */
86 if (lb->num == lb->max) return -1;
87
88 lb->num++;
89 block++;
90
91 block->start = offset;
92 block->end = offset + size;
93 FR_PROTO_TRACE("Appending block (%d) to %u..%u", lb->num - 1, block->start, block->end);
94
95 return 0;
96}
97
98static void dns_label_mark(fr_dns_labels_t *lb, uint8_t const *p)
99{
100 if (!lb || !lb->mark) return;
101
102 fr_assert(p >= (lb->start + 12)); /* can't point to the packet header */
103 fr_assert(!lb->end || (p < lb->end));
104
105 lb->mark[p - lb->start] = 1;
106}
107
108
110{
111 int i;
112
113 if (!lb) return true; /* we have no idea, so allow it */
114
115 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 for (i = 0; i < lb->num; i++) {
123 FR_PROTO_TRACE("Checking block %d %u..%u against %u",
124 i, lb->blocks[i].start, lb->blocks[i].end, offset);
125
126 if (offset < lb->blocks[i].start) return false;
127
128 if (offset < lb->blocks[i].end) return true;
129 }
130
131 return false;
132}
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 */
140static bool labelcmp(uint8_t const *a, uint8_t const *b, size_t len)
141{
142 for (/* nothing */; len > 0; len--) {
143 if (*a == *b) {
144 a++;
145 b++;
146 continue;
147 }
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 if (!(((*a >= 'a') && (*a <= 'z')) || ((*a >= 'A') && (*a <= 'Z')))) return false;
155 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 if (((*a)^(*b)) != 0x20) {
162 return false;
163 }
164
165 a++;
166 b++;
167 }
168
169 return true;
170}
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 */
266static 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{
269 uint8_t *next;
270 uint8_t const *q, *ptr, *suffix, *search;
271 uint16_t offset;
272 bool compressed = false;
273
274 /*
275 * Don't compress "end of label" byte or pointers.
276 */
277 if (!*label || (*label > 63)) {
278 return false;
279 }
280
281 /*
282 * Check the next label. Note that this is *after*
283 * "end". It also MUST be a valid, uncompressed label.
284 */
285 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 if (!new_search) {
302 search = start;
303 } else {
304 search = *new_search;
305 }
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 if (*next == 0x00) {
334 q = search;
335 while (q < end) {
336 if (*q == 0x00) {
337 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 search = q;
346 continue;
347 }
348
349 /*
350 * Our label is a terminal one. Which
351 * can't point to a pointer.
352 */
353 if (*q > 63) {
354 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 search = q;
364 continue;
365 }
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 ptr = q + *q + 1;
375 if (ptr > end) return false;
376
377 /*
378 * Label lengths aren't the same, skip
379 * it.
380 */
381 if (*q != *label) {
382 q = ptr;
383 continue;
384 }
385
386 /*
387 * Our input label ends with 0x00. If
388 * this label doesn't end with 0x00, skip
389 * it.
390 */
391 if (*ptr != 0x00) {
392 q = ptr;
393 continue;
394 }
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 if ((q - packet) > (1 << 14)) return false;
402
403 /*
404 * Only now do case-insensitive
405 * comparisons.
406 */
407 if (!labelcmp(q + 1, label + 1, *label)) {
408 q = ptr;
409 continue;
410 }
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 offset = (q - packet);
422 label[0] = (offset >> 8) | 0xc0;
423 label[1] = offset & 0xff;
424 *label_end = label + 2;
425 if (new_search) *new_search = search;
426 return true;
427 }
428
429 return false;
430 }
431
432 /*
433 * The next label is still uncompressed, so we call
434 * ourselves recursively in order to compress it.
435 */
436 if (*next < 63) {
437 if (!dns_label_compress(packet, start, end, &search, next, label_end)) return false;
438
439 /*
440 * Else it WAS compressed.
441 */
442 compressed = true;
443 }
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 if (*next < 0xc0) {
451 return compressed;
452 }
453
454 /*
455 * Remember where our suffix points to.
456 */
457 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 q = search;
474 while (q < end) {
475 if (*q == 0x00) {
476 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 search = q;
484 continue;
485 }
486
487 /*
488 * Skip compressed pointers. We can't point to
489 * compressed pointers.
490 */
491 if (*q > 63) {
492 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 search = q;
501 continue;
502 }
503
504 /*
505 * We now have an uncompressed label in the input
506 * buffer. Check for a match.
507 */
508 ptr = q + *q + 1;
509 if (ptr > end) return compressed;
510
511 /*
512 * Label lengths aren't the same, skip it.
513 */
514 if (*q != *label) {
515 q = ptr;
516 continue;
517 }
518
519 /*
520 * If the NEXT label is uncompressed, then skip
521 * it unless it's the suffix we're pointing to.
522 */
523 if (*ptr < 63) {
524 if (ptr != suffix) {
525 q = ptr;
526 continue;
527 }
528
529 goto check_label;
530 }
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 if ((ptr[0] != next[0]) ||
539 (ptr[1] != next[1])) {
540 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 search = q;
549 continue;
550 }
551
552 check_label:
553 /*
554 * Pointer is too far away. Don't point
555 * to it.
556 */
557 if ((q - packet) > (1 << 14)) return compressed;
558
559 /*
560 * Only now do case-insensitive
561 * comparisons.
562 */
563 if (!labelcmp(q + 1, label + 1, *label)) {
564 q = ptr;
565 continue;
566 }
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 offset = (q - packet);
578 label[0] = (offset >> 8) | 0xc0;
579 label[1] = offset & 0xff;
580 *label_end = label + 2;
581 if (new_search) *new_search = search;
582 return true;
583 }
584
585 /*
586 * Who knows what it is, we couldn't compress it.
587 */
588 return compressed;
589}
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 */
605{
606 ssize_t slen;
607 size_t need = 0;
608
609 slen = fr_dns_label_from_value_box(&need, dbuff->p, fr_dbuff_remaining(dbuff), dbuff->p, compression, value, lb);
610 if (slen < 0) return 0;
611
612 if (slen == 0) return -need;
613
614 fr_dbuff_advance(dbuff, (size_t)slen);
615 return slen;
616}
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 */
639ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buf_len, uint8_t *where, bool compression,
641{
642 uint8_t *label;
643 uint8_t const *end = buf + buf_len;
644 uint8_t const *q, *strend, *last;
645 uint8_t *data;
646 bool underscore = true;
647
648 if (!buf || !buf_len || !where || !value) {
649 fr_strerror_const("Invalid input");
650 return -1;
651 }
652
653 /*
654 * Don't allow stupidities
655 */
656 if (!((where >= buf) && (where < (buf + buf_len)))) {
657 fr_strerror_const("Label to write is outside of buffer");
658 return -1;
659 }
660
661 /*
662 * We can only encode strings.
663 */
664 if (value->type != FR_TYPE_STRING) {
665 fr_strerror_const("Asked to encode non-string type");
666 return -1;
667 }
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 if ((value->vb_length == 0) ||
676 ((value->vb_length == 1) && (value->vb_strvalue[0] == '.'))) {
677 *where = 0x00;
678 return 1;
679 }
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 q = (uint8_t const *) value->vb_strvalue;
690 strend = q + value->vb_length;
691 last = q;
692
693 if (*q == '.') {
694 fr_strerror_const("Empty labels are invalid");
695 return -1;
696 }
697
698 /*
699 * Convert it piece by piece.
700 */
701 while (q < strend) {
702 /*
703 * Allow underscore at the start of a label.
704 */
705 if (underscore) {
706 underscore = false;
707
708 if (*q == '_') goto next;
709 }
710
711 if (*q == '.') {
712 /*
713 * Don't count final dot as an
714 * intermediate dot, and don't bother
715 * encoding it.
716 */
717 if ((q + 1) == strend) {
718 strend--;
719 break;
720 }
721
722 if (q[1] == '.') {
723 fr_strerror_const("Double dots '..' are forbidden");
724 return -1;
725 }
726 last = q;
727
728 /*
729 * We had a dot, allow underscore as the
730 * first character of the next label.
731 */
732 underscore = true;
733
734 } else if (!((*q == '-') || ((*q >= '0') && (*q <= '9')) ||
735 ((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z')))) {
736 fr_strerror_printf("Invalid character 0x%02x in label", *q);
737 return -1;
738 }
739
740 next:
741 q++;
742
743 if ((q - last) > 63) {
744 fr_strerror_const("Label is larger than 63 characters");
745 return -1;
746 }
747 }
748
749 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 if ((where + (strend - q) + 2) > end) {
760 if (need) *need = (where + (strend - q) + 2) - buf;
761 return 0;
762 }
763
764 label = where;
765 *label = 0;
766 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 while (q < strend) {
774 fr_assert(data < end);
775 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 if (*q == '.') {
787 label = data;
788 *label = 0;
789 data = label + 1;
790
791 q++;
792 continue;
793 }
794
795 *(data++) = *(q++);
796 (*label)++;
797 fr_assert(*label <= 63);
798 }
799
800 *(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 if (compression && ((data - where) > 2)) {
807 if (lb) {
808 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 for (i = 0; i < lb->num; i++) {
833 bool compressed;
834
835 FR_PROTO_TRACE("Trying to compress %s in block %d of %u..%u",
836 value->vb_strvalue, i,
837 lb->blocks[i].start, lb->blocks[i].end);
838
839 compressed = dns_label_compress(lb->start, lb->start + lb->blocks[i].start,
840 lb->start + lb->blocks[i].end,
841 NULL, where, &data);
842 if (compressed) {
843 FR_PROTO_TRACE("Compressed label in block %d", i);
844 if (*(where + *where + 1) >= 0xc0) {
845 FR_PROTO_TRACE("Next label is compressed, stopping");
846 }
847 }
848 }
849
850 dns_label_add(lb, where, data);
851
852 } else if (buf != where) {
853 if (dns_label_compress(buf, buf, where, NULL, where, &data)) {
854 FR_PROTO_TRACE("Compressed single label %s to %zu bytes",
855 value->vb_strvalue, (size_t) (data - where));
856 } else {
857 FR_PROTO_TRACE("Did not compress single label");
858 }
859 }
860 } else {
861 FR_PROTO_TRACE("Not compressing label");
862 }
863
864 fr_assert(data > where);
865 return data - where;
866}
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 */
884ssize_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{
886 uint8_t const *p, *q, *end, *label_end;
887 uint8_t const *current, *start;
888 size_t length;
889 bool at_first_label, already_set_next;
890
891 if (!packet || !buf || (buf_len == 0) || !next) {
892 fr_strerror_printf("Invalid argument");
893 return 0;
894 }
895
896 start = *next;
897
898 /*
899 * Don't allow stupidities
900 */
901 if (!((start >= packet) && (start < (buf + buf_len)))) {
902 fr_strerror_printf("Label is not within the buffer");
903 return 0;
904 }
905
906 end = buf + buf_len;
907 p = current = start;
908 length = 0;
909 at_first_label = true;
910 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 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 if (*p == 0x00) {
925 p++;
926 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 if (current == start) {
934 *next = p;
935 already_set_next = true;
936 }
937
938 break;
939 }
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 if ((p + 1) >= end) goto overflow;
947
948 /*
949 * 0b10 and 0b10 are forbidden
950 */
951 if ((*p > 63) && (*p < 0xc0)) {
952 fr_strerror_const("Data with invalid high bits");
953 return -(p - packet);
954 }
955
956 /*
957 * Maybe it's a compressed pointer.
958 */
959 if (*p > 63) {
960 uint16_t offset;
961
962 if ((p + 2) > end) {
963 overflow:
964 fr_strerror_const("Label overflows buffer");
965 return -(p - packet);
966 }
967
968 offset = p[1];
969 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 if (offset >= (p - packet)) {
993 fr_strerror_printf("Pointer %04x at offset %04x is an invalid forward reference",
994 offset, (unsigned int) (p - packet));
995 return -(p - packet);
996 }
997
998 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 if (q >= current) {
1012 fr_strerror_printf("Pointer %04x at offset %04x creates a loop within a label",
1013 offset, (unsigned int) (p - packet));
1014 return -(p - packet);
1015 }
1016
1017 /*
1018 * If we're tracking which labels are
1019 * valid, then check the pointer, too.
1020 */
1021 if (!dns_pointer_valid(lb, offset)) {
1022 fr_strerror_printf("Pointer %04x at offset %04x does not point to a DNS label",
1023 offset, (unsigned int) (p - packet));
1024 return -(p - packet);
1025 }
1026
1027 /*
1028 * The pointer MUST point to a valid
1029 * length field, and not to another
1030 * pointer.
1031 */
1032 if (*q > 63) {
1033 fr_strerror_printf("Pointer %04x at offset %04x does not point to the start of a label",
1034 offset, (unsigned int) (p - packet));
1035 return -(p - packet);
1036 }
1037
1038 /*
1039 * The pointer MUST NOT point to an end of label field.
1040 */
1041 if (!*q) {
1042 fr_strerror_printf("Pointer %04x at offset %04x refers to an invalid field", offset,
1043 (unsigned int) (p - packet));
1044 return -(p - packet);
1045 }
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 if (current == start) {
1054 *next = p + 2;
1055 already_set_next = true;
1056 }
1057
1058 p = current = q;
1059 continue;
1060 }
1061
1062 /*
1063 * Else it's an uncompressed label
1064 */
1065 if ((p + *p + 1) > end) goto overflow;
1066
1067 /*
1068 * It's a valid label. Mark it as such.
1069 */
1070 dns_label_mark(lb, p);
1071
1072 /*
1073 * Account for the '.' on every label after the
1074 * first one.
1075 */
1076 if (!at_first_label) length++;
1077 at_first_label = false;
1078 length += *p;
1079
1080 /*
1081 * DNS names can be no more than 255 octets.
1082 */
1083 if (length > 255) {
1084 fr_strerror_const("Total length of labels is > 255");
1085 return -(p - packet);
1086 }
1087
1088 q = p + 1;
1089 label_end = q + *p;
1090
1091 /*
1092 * Allow for underscore at the beginning of a
1093 * label.
1094 */
1095 if (*q == '_') q++;
1096
1097 /*
1098 * Verify that the contents of the label are OK.
1099 */
1100 while (q < label_end) {
1101 if (!((*q == '-') || ((*q >= '0') && (*q <= '9')) ||
1102 ((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z')))) {
1103 fr_strerror_printf("Invalid character 0x%02x in label", *q);
1104 return -(q - packet);
1105 }
1106
1107 q++;
1108 }
1109
1110 p += *p + 1;
1111 }
1112
1113 /*
1114 * Return the length of this label.
1115 */
1116 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 if (lb && !lb->mark) (void) dns_label_add(lb, start, *next);
1122
1123 return length;
1124}
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 */
1137ssize_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{
1139 ssize_t slen;
1140 uint8_t const *label = start;
1141 uint8_t const *end = buf + buf_len;
1142
1143 while (label < end) {
1144 if (*label == 0x00) {
1145 label++;
1146 break;
1147 }
1148
1149 slen = fr_dns_label_uncompressed_length(packet, buf, buf_len, &label, lb);
1150 if (slen <= 0) return slen; /* already is offset from 'buf' and not 'label' */
1151 }
1152
1153 return label - buf;
1154}
1155
1156static ssize_t dns_label_decode(uint8_t const *packet, uint8_t const *end, uint8_t const **start, uint8_t const **next)
1157{
1158 uint8_t const *p, *q;
1159
1160 p = *start;
1161
1162 if (end == packet) return -1;
1163
1164 if (*p == 0x00) {
1165 *next = p + 1;
1166 return 0;
1167 }
1168
1169 /*
1170 * Pointer, which points somewhere in the packet.
1171 */
1172 if (*p >= 0xc0) {
1173 uint16_t offset;
1174
1175 if ((end - packet) < 2) {
1176 return -(p - packet);
1177 }
1178
1179 offset = p[1];
1180 offset += ((*p & ~0xc0) << 8);
1181
1182 q = packet + offset;
1183 if (q >= p) {
1184 return -(p - packet);
1185 }
1186 p = q;
1187 }
1188
1189 /*
1190 * 0b10 and 0b10 are forbidden, and pointers can't point to other pointers.
1191 */
1192 if (*p > 63) return -(p - packet);
1193
1194 if ((p + *p + 1) > end) {
1195 return -(p - packet);
1196 }
1197
1198 /*
1199 * Tell the caller where the actual label is located.
1200 */
1201 *start = p;
1202 *next = p + *p + 1;
1203 return *p;
1204}
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 */
1226 uint8_t const *src, size_t len, uint8_t const *label,
1227 bool tainted, fr_dns_labels_t *lb)
1228{
1229 ssize_t slen;
1230 uint8_t const *after = label;
1231 uint8_t const *current, *next = NULL;
1232 uint8_t const *packet = src;
1233 uint8_t const *end = packet + len;
1234 uint8_t *p;
1235 char *q;
1236
1237 if (!len) return -1;
1238
1239 /*
1240 * The label must be within the current buffer we're
1241 * passed.
1242 */
1243 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 if (lb) packet = lb->start;
1250
1251 /*
1252 * Get the uncompressed length of the label, and the
1253 * label after this one.
1254 */
1255 slen = fr_dns_label_uncompressed_length(packet, src, len, &after, lb);
1256 if (slen <= 0) {
1257 FR_PROTO_TRACE("dns_label_to_value_box - Failed getting length");
1258 return slen;
1259 }
1260
1262
1263 /*
1264 * An empty label is a 0x00 byte. Just create an empty
1265 * string.
1266 */
1267 if (slen == 1) {
1268 if (fr_value_box_bstr_alloc(ctx, &q, dst, NULL, 1, tainted) < 0) return -1;
1269 q[0] = '.';
1270 return after - label;
1271 }
1272
1273 /*
1274 * Allocate the string and set up the value_box
1275 */
1276 if (fr_value_box_bstr_alloc(ctx, &q, dst, NULL, slen, tainted) < 0) return -1;
1277
1278 current = label;
1279 p = (uint8_t *) q;
1280 q += slen;
1281
1282 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 slen = dns_label_decode(packet, end, &current, &next);
1288 if (slen < 0) return slen;
1289
1290 /*
1291 * As a sanity check, ensure we don't have a
1292 * buffer overflow.
1293 */
1294 if ((p + slen) > (uint8_t *) q) {
1295 FR_PROTO_TRACE("dns_label_to_value_box - length %zd Failed at %d", slen, __LINE__);
1296
1297 fail:
1298 fr_value_box_clear(dst);
1299 return -1;
1300 }
1301
1302 /*
1303 * Add '.' before the label, but only for the
1304 * second and subsequent labels.
1305 */
1306 if (p != (uint8_t const *) dst->vb_strvalue) {
1307 *(p++) = '.';
1308 }
1309
1310 /*
1311 * Copy the raw bytes from the network.
1312 */
1313 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 p += slen;
1320 current = next;
1321 }
1322
1323 /*
1324 * As a last sanity check, ensure that we've filled the
1325 * buffer exactly.
1326 */
1327 if (p != (uint8_t *) q) {
1328 FR_PROTO_TRACE("dns_label_to_value_box - Failed at %d", __LINE__);
1329 goto fail;
1330 }
1331
1332 *p = '\0';
1333
1334 /*
1335 * Return the number of network bytes used to parse this
1336 * part of the label.
1337 */
1338 return after - label;
1339}
#define RCSID(id)
Definition build.h:483
#define fr_dbuff_advance(_dbuff_or_marker, _len)
Advance 'current' position in dbuff or marker by _len bytes.
Definition dbuff.h:1072
#define fr_dbuff_remaining(_dbuff_or_marker)
Return the number of bytes remaining between the dbuff or marker and the end of the buffer.
Definition dbuff.h:743
Test enumeration values.
Definition dict_test.h:92
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)
Get the uncompressed length of a DNS label in a network buffer.
Definition dns.c:884
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)
Encode a single value box of type string, serializing its contents to a dns label in a dbuff.
Definition dns.c:604
#define MAX_OFFSET
Definition dns.c:32
ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buf_len, uint8_t *where, bool compression, fr_value_box_t const *value, fr_dns_labels_t *lb)
Encode a single value box of type string, serializing its contents to a dns label.
Definition dns.c:639
static void dns_label_mark(fr_dns_labels_t *lb, uint8_t const *p)
Definition dns.c:98
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)
Verify that a network buffer contains valid DNS labels.
Definition dns.c:1137
static int dns_label_add(fr_dns_labels_t *lb, uint8_t const *start, uint8_t const *end)
Definition dns.c:34
ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst, uint8_t const *src, size_t len, uint8_t const *label, bool tainted, fr_dns_labels_t *lb)
Decode a fr_value_box_t from one DNS label.
Definition dns.c:1225
static bool dns_label_compress(uint8_t const *packet, uint8_t const *start, uint8_t const *end, uint8_t const **new_search, uint8_t *label, uint8_t **label_end)
Compress "label" by looking at the label recursively.
Definition dns.c:266
static bool dns_pointer_valid(fr_dns_labels_t *lb, uint16_t offset)
Definition dns.c:109
static ssize_t dns_label_decode(uint8_t const *packet, uint8_t const *end, uint8_t const **start, uint8_t const **next)
Definition dns.c:1156
static bool labelcmp(uint8_t const *a, uint8_t const *b, size_t len)
Compare two labels in a case-insensitive fashion.
Definition dns.c:140
uint16_t start
Definition dns.h:31
uint8_t const * start
start of packet
Definition dns.h:36
uint8_t const * end
end of the packet
Definition dns.h:37
fr_dns_block_t * blocks
maximum number of labels
Definition dns.h:41
uint8_t * mark
markup buffer used for decoding.
Definition dns.h:38
uint16_t end
Definition dns.h:32
int num
number of used labels
Definition dns.h:39
unsigned short uint16_t
@ FR_TYPE_STRING
String of printable characters.
uint8_t * p
long int ssize_t
unsigned char uint8_t
#define fr_assert(_expr)
Definition rad_assert.h:38
static rc_request_t * current
#define FR_PROTO_TRACE(_fmt,...)
Definition proto.h:40
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223
int fr_value_box_bstr_alloc(TALLOC_CTX *ctx, char **out, fr_value_box_t *dst, fr_dict_attr_t const *enumv, size_t len, bool tainted)
Alloc and assign an empty \0 terminated string to a fr_value_box_t.
Definition value.c:4071
void fr_value_box_clear(fr_value_box_t *data)
Clear/free any existing value and metadata.
Definition value.c:3723
static fr_slen_t data
Definition value.h:1265
#define fr_value_box_init_null(_vb)
Initialise an empty/null box that will be filled later.
Definition value.h:593