/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: 4c6483ae1fc93c20b9cd6bd8e84aa3828e2cf94b $") |
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 | 40.1k | { |
100 | 40.1k | if (!lb || !lb->mark) return; |
101 | | |
102 | 15.6k | fr_assert(p >= (lb->start + 12)); /* can't point to the packet header */ |
103 | 15.6k | fr_assert(!lb->end || (p < lb->end)); |
104 | | |
105 | 15.6k | lb->mark[p - lb->start] = 1; |
106 | 15.6k | } |
107 | | |
108 | | |
109 | | static bool dns_pointer_valid(fr_dns_labels_t *lb, uint16_t offset) |
110 | 9.66k | { |
111 | 9.66k | int i; |
112 | | |
113 | 9.66k | if (!lb) return true; /* we have no idea, so allow it */ |
114 | | |
115 | 1.51k | 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) >= MAX_OFFSET) 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) >= MAX_OFFSET) 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 error |
602 | | */ |
603 | | 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) |
604 | 0 | { |
605 | 0 | ssize_t slen; |
606 | 0 | size_t need = 0; |
607 | |
|
608 | 0 | slen = fr_dns_label_from_value_box(&need, dbuff->p, fr_dbuff_remaining(dbuff), dbuff->p, compression, value, lb); |
609 | 0 | if (slen < 0) return -need; |
610 | | |
611 | 0 | fr_dbuff_advance(dbuff, (size_t) slen); |
612 | 0 | return slen; |
613 | 0 | } |
614 | | |
615 | | /** Encode a single value box of type string, serializing its contents to a dns label |
616 | | * |
617 | | * This functions takes a large buffer and encodes the label in part |
618 | | * of the buffer. This API is necessary in order to allow DNS label |
619 | | * compression. |
620 | | * |
621 | | * @param[out] need if not NULL, how long "buf_len" should be to |
622 | | * serialize the rest of the data. |
623 | | * Note: Only variable length types will be partially |
624 | | * encoded. Fixed length types will not be partially encoded. |
625 | | * @param[out] buf Buffer where labels are stored |
626 | | * @param[in] buf_len The length of the output buffer |
627 | | * @param[out] where Where to write this label |
628 | | * @param[in] compression Whether or not to do DNS label compression. |
629 | | * @param[in] value to encode. |
630 | | * @param[in] lb label tracking data structure |
631 | | * @return |
632 | | * - 0 no bytes were written, see need value to determine |
633 | | * - >0 the number of bytes written to "where", NOT "buf + where + outlen" |
634 | | * - <0 on error. |
635 | | */ |
636 | | ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buf_len, uint8_t *where, bool compression, |
637 | | fr_value_box_t const *value, fr_dns_labels_t *lb) |
638 | 0 | { |
639 | 0 | uint8_t *label; |
640 | 0 | uint8_t const *end; |
641 | 0 | uint8_t const *q, *strend, *last; |
642 | 0 | uint8_t *data; |
643 | 0 | bool underscore = true; |
644 | |
|
645 | 0 | if (!buf || !buf_len || !where || !value) { |
646 | 0 | fr_strerror_const("Invalid input"); |
647 | 0 | return -1; |
648 | 0 | } |
649 | | |
650 | 0 | end = buf + buf_len; |
651 | | |
652 | | /* |
653 | | * Don't allow stupidities |
654 | | */ |
655 | 0 | if (!((where >= buf) && (where < (buf + buf_len)))) { |
656 | 0 | fr_strerror_const("Label to write is outside of buffer"); |
657 | 0 | return -1; |
658 | 0 | } |
659 | | |
660 | | /* |
661 | | * We can only encode strings. |
662 | | */ |
663 | 0 | if (value->type != FR_TYPE_STRING) { |
664 | 0 | fr_strerror_const("Asked to encode non-string type"); |
665 | 0 | return -1; |
666 | 0 | } |
667 | | |
668 | | /* |
669 | | * '.' or empty string is special, and is encoded as a |
670 | | * plain zero byte. |
671 | | * |
672 | | * Since "where < end", we can always write 1 byte to it. |
673 | | */ |
674 | 0 | if ((value->vb_length == 0) || |
675 | 0 | ((value->vb_length == 1) && (value->vb_strvalue[0] == '.'))) { |
676 | 0 | *where = 0x00; |
677 | 0 | return 1; |
678 | 0 | } |
679 | | |
680 | | /* |
681 | | * Sanity check the name before writing anything to the |
682 | | * buffer. |
683 | | * |
684 | | * Only encode [-0-9a-zA-Z]. Anything else is forbidden. |
685 | | * Dots at the start are forbidden. Double dots are |
686 | | * forbidden. |
687 | | */ |
688 | 0 | q = (uint8_t const *) value->vb_strvalue; |
689 | 0 | strend = q + value->vb_length; |
690 | 0 | last = q; |
691 | |
|
692 | 0 | if (*q == '.') { |
693 | 0 | fr_strerror_const("Empty labels are invalid"); |
694 | 0 | return -1; |
695 | 0 | } |
696 | | |
697 | | /* |
698 | | * Convert it piece by piece. |
699 | | */ |
700 | 0 | while (q < strend) { |
701 | | /* |
702 | | * Allow underscore at the start of a label. |
703 | | */ |
704 | 0 | if (underscore) { |
705 | 0 | underscore = false; |
706 | |
|
707 | 0 | if (*q == '_') goto next; |
708 | 0 | } |
709 | | |
710 | 0 | if (*q == '.') { |
711 | | /* |
712 | | * Don't count final dot as an |
713 | | * intermediate dot, and don't bother |
714 | | * encoding it. |
715 | | */ |
716 | 0 | if ((q + 1) == strend) { |
717 | 0 | strend--; |
718 | 0 | break; |
719 | 0 | } |
720 | | |
721 | 0 | if (q[1] == '.') { |
722 | 0 | fr_strerror_const("Double dots '..' are forbidden"); |
723 | 0 | return -1; |
724 | 0 | } |
725 | 0 | last = q; |
726 | | |
727 | | /* |
728 | | * We had a dot, allow underscore as the |
729 | | * first character of the next label. |
730 | | */ |
731 | 0 | underscore = true; |
732 | |
|
733 | 0 | } else if (!((*q == '-') || ((*q >= '0') && (*q <= '9')) || |
734 | 0 | ((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z')))) { |
735 | 0 | fr_strerror_printf("Invalid character 0x%02x in label", *q); |
736 | 0 | return -1; |
737 | 0 | } |
738 | | |
739 | 0 | next: |
740 | 0 | q++; |
741 | |
|
742 | 0 | if ((q - last) > 63) { |
743 | 0 | fr_strerror_const("Label is larger than 63 characters"); |
744 | 0 | return -1; |
745 | 0 | } |
746 | 0 | } |
747 | | |
748 | 0 | q = (uint8_t const *) value->vb_strvalue; |
749 | | |
750 | | /* |
751 | | * For now, just encode the value as-is. We do |
752 | | * compression as a second step. |
753 | | * |
754 | | * We need a minimum length of string + beginning length |
755 | | * + trailing zero. Intermediate '.' are converted to |
756 | | * length bytes. |
757 | | */ |
758 | 0 | if ((where + (strend - q) + 2) > end) { |
759 | 0 | if (need) *need = (where + (strend - q) + 2) - buf; |
760 | 0 | return 0; |
761 | 0 | } |
762 | | |
763 | 0 | label = where; |
764 | 0 | *label = 0; |
765 | 0 | data = label + 1; |
766 | | |
767 | | /* |
768 | | * @todo - encode into a local buffer, and then try to |
769 | | * compress that into the output buffer. This means that |
770 | | * the output buffer can be a little bit smaller. |
771 | | */ |
772 | 0 | while (q < strend) { |
773 | 0 | fr_assert(data < end); |
774 | 0 | fr_assert((data - where) < 255); |
775 | | |
776 | | /* |
777 | | * '.' is a label delimiter. |
778 | | * |
779 | | * We've already checked above for '.' at the |
780 | | * start, for double dots, and have already |
781 | | * suppressed '.' at the end of the string. |
782 | | * |
783 | | * Start a new label. |
784 | | */ |
785 | 0 | if (*q == '.') { |
786 | 0 | label = data; |
787 | 0 | *label = 0; |
788 | 0 | data = label + 1; |
789 | |
|
790 | 0 | q++; |
791 | 0 | continue; |
792 | 0 | } |
793 | | |
794 | 0 | *(data++) = *(q++); |
795 | 0 | (*label)++; |
796 | 0 | fr_assert(*label <= 63); |
797 | 0 | } |
798 | |
|
799 | 0 | *(data++) = 0; /* end of label */ |
800 | | |
801 | | /* |
802 | | * If we're compressing it, and we have data to compress, |
803 | | * then do it. |
804 | | */ |
805 | 0 | if (compression && ((data - where) > 2)) { |
806 | 0 | if (lb) { |
807 | 0 | int i; |
808 | | |
809 | | /* |
810 | | * Loop over the parts of the packet which have DNS labels. |
811 | | * |
812 | | * Note that the dns_label_compress() function does NOT follow pointers in the |
813 | | * start/end block which it's searching! It just tries to compress the *input*, |
814 | | * and assumes that the input is compressed last label to first label. |
815 | | * |
816 | | * In addition, dns_label_compress() tracks where in the block it started |
817 | | * searching. So it only scans the block once, even if we pass a NULL search |
818 | | * parameter to it. |
819 | | * |
820 | | * We could start compression from the *last* block. When we add |
821 | | * "www.example.com" and then "ftp.example.com", we could point "ftp" to the |
822 | | * "example.com" portion. which is already in the packet. However, doing that |
823 | | * would require that dns_label_compress() follows pointers in the block it's |
824 | | * searching. Which would greatly increase the complexity of the algorithm. |
825 | | * |
826 | | * |
827 | | * We could still optimize this algorithm a bit, by tracking which parts of the |
828 | | * buffer have DNS names of label length 1, 2, etc. Doing that would mean more |
829 | | * complex data structures, but fewer passes over the packet. |
830 | | */ |
831 | 0 | for (i = 0; i < lb->num; i++) { |
832 | 0 | bool compressed; |
833 | |
|
834 | 0 | FR_PROTO_TRACE("Trying to compress %s in block %d of %u..%u", |
835 | 0 | value->vb_strvalue, i, |
836 | 0 | lb->blocks[i].start, lb->blocks[i].end); |
837 | |
|
838 | 0 | compressed = dns_label_compress(lb->start, lb->start + lb->blocks[i].start, |
839 | 0 | lb->start + lb->blocks[i].end, |
840 | 0 | NULL, where, &data); |
841 | 0 | if (compressed) { |
842 | 0 | FR_PROTO_TRACE("Compressed label in block %d", i); |
843 | 0 | if (*(where + *where + 1) >= 0xc0) { |
844 | 0 | FR_PROTO_TRACE("Next label is compressed, stopping"); |
845 | 0 | } |
846 | 0 | } |
847 | 0 | } |
848 | |
|
849 | 0 | dns_label_add(lb, where, data); |
850 | |
|
851 | 0 | } else if (buf != where) { |
852 | 0 | if (dns_label_compress(buf, buf, where, NULL, where, &data)) { |
853 | 0 | FR_PROTO_TRACE("Compressed single label %s to %zu bytes", |
854 | 0 | value->vb_strvalue, (size_t) (data - where)); |
855 | 0 | } else { |
856 | 0 | FR_PROTO_TRACE("Did not compress single label"); |
857 | 0 | } |
858 | 0 | } |
859 | 0 | } else { |
860 | 0 | FR_PROTO_TRACE("Not compressing label"); |
861 | 0 | } |
862 | |
|
863 | 0 | fr_assert(data > where); |
864 | 0 | return data - where; |
865 | 0 | } |
866 | | |
867 | | /** Get the *uncompressed* length of a DNS label in a network buffer. |
868 | | * |
869 | | * i.e. how bytes are required to store the uncompressed version of |
870 | | * the label. |
871 | | * |
872 | | * Note that a bare 0x00 byte has length 1, to account for '.' |
873 | | * |
874 | | * @param[in] packet where the packet starts |
875 | | * @param[in] buf buffer holding one or more DNS labels |
876 | | * @param[in] buf_len total length of the buffer |
877 | | * @param[in,out] next the DNS label to check, updated to point to the next label |
878 | | * @param[in] lb label tracking data structure |
879 | | * @return |
880 | | * - <=0 on error, offset from buf where the invalid label is located. |
881 | | * - > 0 decoded size of this particular DNS label |
882 | | */ |
883 | | 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) |
884 | 94.3k | { |
885 | 94.3k | uint8_t const *p, *q, *end, *label_end; |
886 | 94.3k | uint8_t const *current, *start; |
887 | 94.3k | size_t length; |
888 | 94.3k | bool at_first_label, already_set_next; |
889 | | |
890 | 94.3k | if (!packet || !buf || (buf_len == 0) || !next) { |
891 | 1.82k | fr_strerror_printf("Invalid argument"); |
892 | 1.82k | return 0; |
893 | 1.82k | } |
894 | | |
895 | 92.5k | start = *next; |
896 | | |
897 | | /* |
898 | | * Don't allow stupidities |
899 | | */ |
900 | 92.5k | if (!((start >= packet) && (start < (buf + buf_len)))) { |
901 | 0 | fr_strerror_printf("Label is not within the buffer"); |
902 | 0 | return 0; |
903 | 0 | } |
904 | | |
905 | 92.5k | end = buf + buf_len; |
906 | 92.5k | p = current = start; |
907 | 92.5k | length = 0; |
908 | 92.5k | at_first_label = true; |
909 | 92.5k | already_set_next = false; |
910 | | |
911 | | /* |
912 | | * We silently accept labels *without* a trailing 0x00, |
913 | | * so long as they end at the end of the input buffer. |
914 | | */ |
915 | 139k | while (p < end) { |
916 | | /* |
917 | | * End of label byte. Skip it. |
918 | | * |
919 | | * Empty labels are length 1, to account for the |
920 | | * '.'. The caller has to take care of this |
921 | | * manually. |
922 | | */ |
923 | 136k | if (*p == 0x00) { |
924 | 80.0k | p++; |
925 | 80.0k | if (at_first_label) length++; |
926 | | |
927 | | /* |
928 | | * We're still processing the first |
929 | | * label, tell the caller where the next |
930 | | * one is located. |
931 | | */ |
932 | 80.0k | if (current == start) { |
933 | 74.9k | *next = p; |
934 | 74.9k | already_set_next = true; |
935 | 74.9k | } |
936 | | |
937 | 80.0k | break; |
938 | 80.0k | } |
939 | | |
940 | | /* |
941 | | * If there's only one byte in the packet, then |
942 | | * it MUST be 0x00. If it's not, then the label |
943 | | * overflows the buffer. |
944 | | */ |
945 | 56.1k | if ((p + 1) >= end) goto overflow; |
946 | | |
947 | | /* |
948 | | * 0b01 and 0b10 are forbidden |
949 | | */ |
950 | 53.4k | if ((*p > 63) && (*p < 0xc0)) { |
951 | 1.18k | fr_strerror_const("Data with invalid high bits"); |
952 | 1.18k | return -(p - packet); |
953 | 1.18k | } |
954 | | |
955 | | /* |
956 | | * Maybe it's a compressed pointer. |
957 | | */ |
958 | 52.3k | if (*p > 63) { |
959 | 11.1k | uint16_t offset; |
960 | | |
961 | 11.1k | if ((p + 2) > end) { |
962 | 3.68k | overflow: |
963 | 3.68k | fr_strerror_const("Label overflows buffer"); |
964 | 3.68k | return -(p - packet); |
965 | 0 | } |
966 | | |
967 | 11.1k | offset = p[1]; |
968 | 11.1k | offset += ((*p & ~0xc0) << 8); |
969 | | |
970 | | /* |
971 | | * Forward references are forbidden, |
972 | | * including self-references. |
973 | | * |
974 | | * This requirement follows RFC 1035 |
975 | | * Section 4.1.4, which says: |
976 | | * |
977 | | * ... an entire domain name or a list of |
978 | | * labels at the end of a domain name is |
979 | | * replaced with a pointer to a prior |
980 | | * occurrence of the same name. |
981 | | * ... |
982 | | * |
983 | | * Note the key word PRIOR. If we |
984 | | * enforce that the pointer is backwards, |
985 | | * and do various other enforcements, |
986 | | * then it is very difficult for |
987 | | * attackers to create malicious DNS |
988 | | * packets which will cause the decoder |
989 | | * to do bad things. |
990 | | */ |
991 | 11.1k | if (offset >= (p - packet)) { |
992 | 1.02k | fr_strerror_printf("Pointer %04x at offset %04x is an invalid forward reference", |
993 | 1.02k | offset, (unsigned int) (p - packet)); |
994 | 1.02k | return -(p - packet); |
995 | 1.02k | } |
996 | | |
997 | 10.0k | q = packet + offset; |
998 | | |
999 | | /* |
1000 | | * As an additional sanity check, the |
1001 | | * pointer MUST NOT point to something |
1002 | | * within the label we're parsing. If |
1003 | | * that happens, we have a loop. |
1004 | | * |
1005 | | * i.e. the pointer must point backwards |
1006 | | * to *before* our current label. When |
1007 | | * that limitation is enforced, pointer |
1008 | | * loops are impossible. |
1009 | | */ |
1010 | 10.0k | if (q >= current) { |
1011 | 430 | fr_strerror_printf("Pointer %04x at offset %04x creates a loop within a label", |
1012 | 430 | offset, (unsigned int) (p - packet)); |
1013 | 430 | return -(p - packet); |
1014 | 430 | } |
1015 | | |
1016 | | /* |
1017 | | * If we're tracking which labels are |
1018 | | * valid, then check the pointer, too. |
1019 | | */ |
1020 | 9.66k | if (!dns_pointer_valid(lb, offset)) { |
1021 | 95 | fr_strerror_printf("Pointer %04x at offset %04x does not point to a DNS label", |
1022 | 95 | offset, (unsigned int) (p - packet)); |
1023 | 95 | return -(p - packet); |
1024 | 95 | } |
1025 | | |
1026 | | /* |
1027 | | * The pointer MUST point to a valid |
1028 | | * length field, and not to another |
1029 | | * pointer. |
1030 | | */ |
1031 | 9.57k | if (*q > 63) { |
1032 | 357 | fr_strerror_printf("Pointer %04x at offset %04x does not point to the start of a label", |
1033 | 357 | offset, (unsigned int) (p - packet)); |
1034 | 357 | return -(p - packet); |
1035 | 357 | } |
1036 | | |
1037 | | /* |
1038 | | * The pointer MUST NOT point to an end of label field. |
1039 | | */ |
1040 | 9.21k | if (!*q) { |
1041 | 348 | fr_strerror_printf("Pointer %04x at offset %04x refers to an invalid field", offset, |
1042 | 348 | (unsigned int) (p - packet)); |
1043 | 348 | return -(p - packet); |
1044 | 348 | } |
1045 | | |
1046 | | /* |
1047 | | * If we're jumping away from the label |
1048 | | * we started with, tell the caller where |
1049 | | * the next label is in the network |
1050 | | * buffer. |
1051 | | */ |
1052 | 8.86k | if (current == start) { |
1053 | 5.52k | *next = p + 2; |
1054 | 5.52k | already_set_next = true; |
1055 | 5.52k | } |
1056 | | |
1057 | 8.86k | p = current = q; |
1058 | 8.86k | continue; |
1059 | 9.21k | } |
1060 | | |
1061 | | /* |
1062 | | * Else it's an uncompressed label |
1063 | | */ |
1064 | 41.1k | if ((p + *p + 1) > end) goto overflow; |
1065 | | |
1066 | | /* |
1067 | | * It's a valid label. Mark it as such. |
1068 | | */ |
1069 | 40.1k | dns_label_mark(lb, p); |
1070 | | |
1071 | | /* |
1072 | | * Account for the '.' on every label after the |
1073 | | * first one. |
1074 | | */ |
1075 | 40.1k | if (!at_first_label) length++; |
1076 | 40.1k | at_first_label = false; |
1077 | 40.1k | length += *p; |
1078 | | |
1079 | | /* |
1080 | | * DNS names can be no more than 255 octets. |
1081 | | */ |
1082 | 40.1k | if (length > 255) { |
1083 | 145 | fr_strerror_const("Total length of labels is > 255"); |
1084 | 145 | return -(p - packet); |
1085 | 145 | } |
1086 | | |
1087 | 40.0k | q = p + 1; |
1088 | 40.0k | label_end = q + *p; |
1089 | | |
1090 | | /* |
1091 | | * Allow for underscore at the beginning of a |
1092 | | * label. |
1093 | | */ |
1094 | 40.0k | if (*q == '_') q++; |
1095 | | |
1096 | | /* |
1097 | | * Verify that the contents of the label are OK. |
1098 | | */ |
1099 | 108k | while (q < label_end) { |
1100 | 70.8k | if (!((*q == '-') || ((*q >= '0') && (*q <= '9')) || |
1101 | 27.3k | ((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z')))) { |
1102 | 2.19k | fr_strerror_printf("Invalid character 0x%02x in label", *q); |
1103 | 2.19k | return -(q - packet); |
1104 | 2.19k | } |
1105 | | |
1106 | 68.6k | q++; |
1107 | 68.6k | } |
1108 | | |
1109 | 37.8k | p += *p + 1; |
1110 | 37.8k | } |
1111 | | |
1112 | | /* |
1113 | | * Return the length of this label. |
1114 | | */ |
1115 | 83.0k | if (!already_set_next) *next = p; /* should be <='end' */ |
1116 | | |
1117 | | /* |
1118 | | * Add the label, only if we're not using the markup field. |
1119 | | */ |
1120 | 83.0k | if (lb && !lb->mark) (void) dns_label_add(lb, start, *next); |
1121 | | |
1122 | 83.0k | return length; |
1123 | 92.5k | } |
1124 | | |
1125 | | /** Verify that a network buffer contains valid DNS labels. |
1126 | | * |
1127 | | * @param[in] packet where the packet starts |
1128 | | * @param[in] buf buffer holding one or more DNS labels |
1129 | | * @param[in] buf_len total length of the buffer |
1130 | | * @param[in] start where to start looking |
1131 | | * @param[in] lb label tracking data structure |
1132 | | * @return |
1133 | | * - <=0 on error, where in the buffer the invalid label is located. |
1134 | | * - > 0 total size of the encoded label(s). Will be <= buf_len |
1135 | | */ |
1136 | | 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) |
1137 | 10.8k | { |
1138 | 10.8k | ssize_t slen; |
1139 | 10.8k | uint8_t const *label = start; |
1140 | 10.8k | uint8_t const *end = buf + buf_len; |
1141 | | |
1142 | 15.6k | while (label < end) { |
1143 | 13.3k | if (*label == 0x00) { |
1144 | 3.45k | label++; |
1145 | 3.45k | break; |
1146 | 3.45k | } |
1147 | | |
1148 | 9.86k | slen = fr_dns_label_uncompressed_length(packet, buf, buf_len, &label, lb); |
1149 | 9.86k | if (slen <= 0) return slen; /* already is offset from 'buf' and not 'label' */ |
1150 | 9.86k | } |
1151 | | |
1152 | 5.77k | return label - buf; |
1153 | 10.8k | } |
1154 | | |
1155 | | static ssize_t dns_label_decode(uint8_t const *packet, uint8_t const *end, uint8_t const **start, uint8_t const **next) |
1156 | 10.2k | { |
1157 | 10.2k | uint8_t const *p, *q; |
1158 | | |
1159 | 10.2k | p = *start; |
1160 | | |
1161 | 10.2k | if (end == packet) return -1; |
1162 | | |
1163 | 10.2k | if (*p == 0x00) { |
1164 | 0 | *next = p + 1; |
1165 | 0 | return 0; |
1166 | 0 | } |
1167 | | |
1168 | | /* |
1169 | | * Pointer, which points somewhere in the packet. |
1170 | | */ |
1171 | 10.2k | if (*p >= 0xc0) { |
1172 | 2.68k | uint16_t offset; |
1173 | | |
1174 | 2.68k | if ((end - p) < 2) { |
1175 | 0 | return -(p - packet); |
1176 | 0 | } |
1177 | | |
1178 | 2.68k | offset = p[1]; |
1179 | 2.68k | offset += ((*p & ~0xc0) << 8); |
1180 | | |
1181 | 2.68k | q = packet + offset; |
1182 | 2.68k | if (q >= p) { |
1183 | 0 | return -(p - packet); |
1184 | 0 | } |
1185 | 2.68k | p = q; |
1186 | 2.68k | } |
1187 | | |
1188 | | /* |
1189 | | * 0b01 and 0b10 are forbidden, and pointers can't point to other pointers. |
1190 | | */ |
1191 | 10.2k | if (*p > 63) return -(p - packet); |
1192 | | |
1193 | 10.2k | if ((p + *p + 1) > end) { |
1194 | 0 | return -(p - packet); |
1195 | 0 | } |
1196 | | |
1197 | | /* |
1198 | | * Tell the caller where the actual label is located. |
1199 | | */ |
1200 | 10.2k | *start = p; |
1201 | 10.2k | *next = p + *p + 1; |
1202 | 10.2k | return *p; |
1203 | 10.2k | } |
1204 | | |
1205 | | |
1206 | | /** Decode a #fr_value_box_t from one DNS label |
1207 | | * |
1208 | | * The output type is always FR_TYPE_STRING |
1209 | | * |
1210 | | * Note that the caller MUST call fr_dns_labels_network_verify(src, len, start) |
1211 | | * before calling this function. Otherwise bad things will happen. |
1212 | | * |
1213 | | * @param[in] ctx Where to allocate any talloc buffers required. |
1214 | | * @param[out] dst value_box to write the result to. |
1215 | | * @param[in] src Start of the buffer containing DNS labels |
1216 | | * @param[in] len Length of the buffer to decode |
1217 | | * @param[in] label This particular label |
1218 | | * @param[in] tainted Whether the value came from a trusted source. |
1219 | | * @param[in] lb label tracking data structure |
1220 | | * @return |
1221 | | * - >= 0 The number of network bytes consumed. |
1222 | | * - <0 on error. |
1223 | | */ |
1224 | | ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst, |
1225 | | uint8_t const *src, size_t len, uint8_t const *label, |
1226 | | bool tainted, fr_dns_labels_t *lb) |
1227 | 41.9k | { |
1228 | 41.9k | ssize_t slen; |
1229 | 41.9k | uint8_t const *after = label; |
1230 | 41.9k | uint8_t const *current, *next = NULL; |
1231 | 41.9k | uint8_t const *packet = src; |
1232 | 41.9k | uint8_t const *end = packet + len; |
1233 | 41.9k | uint8_t *p; |
1234 | 41.9k | char *q; |
1235 | | |
1236 | 41.9k | if (!len) return -1; |
1237 | | |
1238 | | /* |
1239 | | * The label must be within the current buffer we're |
1240 | | * passed. |
1241 | | */ |
1242 | 41.9k | if ((label < src) || (label >= end)) return -1; |
1243 | | |
1244 | | /* |
1245 | | * The actual packet might start earlier than the buffer, |
1246 | | * so reset it if necessary. |
1247 | | */ |
1248 | 41.9k | if (lb) packet = lb->start; |
1249 | | |
1250 | | /* |
1251 | | * Get the uncompressed length of the label, and the |
1252 | | * label after this one. |
1253 | | */ |
1254 | 41.9k | slen = fr_dns_label_uncompressed_length(packet, src, len, &after, lb); |
1255 | 41.9k | if (slen <= 0) { |
1256 | 0 | FR_PROTO_TRACE("dns_label_to_value_box - Failed getting length"); |
1257 | 0 | return slen; |
1258 | 0 | } |
1259 | | |
1260 | 41.9k | fr_value_box_init_null(dst); |
1261 | | |
1262 | | /* |
1263 | | * An empty label is a 0x00 byte. Just create an empty |
1264 | | * string. |
1265 | | */ |
1266 | 41.9k | if (slen == 1) { |
1267 | 39.4k | if (fr_value_box_bstr_alloc(ctx, &q, dst, NULL, 1, tainted) < 0) return -1; |
1268 | 39.4k | q[0] = '.'; |
1269 | 39.4k | return after - label; |
1270 | 39.4k | } |
1271 | | |
1272 | | /* |
1273 | | * Allocate the string and set up the value_box |
1274 | | */ |
1275 | 2.43k | if (fr_value_box_bstr_alloc(ctx, &q, dst, NULL, slen, tainted) < 0) return -1; |
1276 | | |
1277 | 2.43k | current = label; |
1278 | 2.43k | p = (uint8_t *) q; |
1279 | 2.43k | q += slen; |
1280 | | |
1281 | 12.6k | while (current && (current < after) && (*current != 0x00)) { |
1282 | | /* |
1283 | | * Get how many bytes this label has, and where |
1284 | | * we will go to obtain the next label. |
1285 | | */ |
1286 | 10.2k | slen = dns_label_decode(packet, end, ¤t, &next); |
1287 | 10.2k | if (slen < 0) return slen; |
1288 | | |
1289 | | /* |
1290 | | * As a sanity check, ensure we don't have a |
1291 | | * buffer overflow. |
1292 | | */ |
1293 | 10.2k | if ((p + slen) > (uint8_t *) q) { |
1294 | 0 | FR_PROTO_TRACE("dns_label_to_value_box - length %zd Failed at %d", slen, __LINE__); |
1295 | |
|
1296 | 0 | fail: |
1297 | 0 | fr_value_box_clear(dst); |
1298 | 0 | return -1; |
1299 | 0 | } |
1300 | | |
1301 | | /* |
1302 | | * Add '.' before the label, but only for the |
1303 | | * second and subsequent labels. |
1304 | | */ |
1305 | 10.2k | if (p != (uint8_t const *) dst->vb_strvalue) { |
1306 | 7.80k | *(p++) = '.'; |
1307 | 7.80k | } |
1308 | | |
1309 | | /* |
1310 | | * Copy the raw bytes from the network. |
1311 | | */ |
1312 | 10.2k | memcpy(p, current + 1, slen); |
1313 | | |
1314 | | /* |
1315 | | * Go ahead in the output string, and go to the |
1316 | | * next label for decoding. |
1317 | | */ |
1318 | 10.2k | p += slen; |
1319 | 10.2k | current = next; |
1320 | 10.2k | } |
1321 | | |
1322 | | /* |
1323 | | * As a last sanity check, ensure that we've filled the |
1324 | | * buffer exactly. |
1325 | | */ |
1326 | 2.43k | if (p != (uint8_t *) q) { |
1327 | 0 | FR_PROTO_TRACE("dns_label_to_value_box - Failed at %d", __LINE__); |
1328 | 0 | goto fail; |
1329 | 0 | } |
1330 | | |
1331 | 2.43k | *p = '\0'; |
1332 | | |
1333 | | /* |
1334 | | * Return the number of network bytes used to parse this |
1335 | | * part of the label. |
1336 | | */ |
1337 | 2.43k | return after - label; |
1338 | 2.43k | } |