Coverage Report

Created: 2026-11-12 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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, &current, &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
}