From: Alan T. DeKok Date: Wed, 16 Mar 2022 21:05:44 +0000 (-0400) Subject: add dns_label encoder / decoder, with small tests X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e270625bd1fc5457908af8be8bc7bf0266b10c90;p=thirdparty%2Ffreeradius-server.git add dns_label encoder / decoder, with small tests some options have compressed DNS labels, and that isn't yet supported. --- diff --git a/share/dictionary/dhcpv4/dictionary.rfc4280 b/share/dictionary/dhcpv4/dictionary.rfc4280 index 3610d5f8be5..93a17244f25 100644 --- a/share/dictionary/dhcpv4/dictionary.rfc4280 +++ b/share/dictionary/dhcpv4/dictionary.rfc4280 @@ -11,5 +11,5 @@ # ############################################################################## -ATTRIBUTE BCMS-Server-IPv4-FQDN 88 octets # really dns_label array +ATTRIBUTE BCMS-Server-IPv4-FQDN 88 string dns_label,array ATTRIBUTE BCMS-Server-IPv4-Address 89 ipaddr array diff --git a/src/protocols/dhcpv4/decode.c b/src/protocols/dhcpv4/decode.c index 120db27aa00..7702411d0d8 100644 --- a/src/protocols/dhcpv4/decode.c +++ b/src/protocols/dhcpv4/decode.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "dhcpv4.h" #include "attrs.h" @@ -44,6 +45,10 @@ static ssize_t decode_array(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, uint8_t const *data, size_t data_len, void *decode_ctx, bool exact); +static ssize_t decode_dns_labels(TALLOC_CTX *ctx, fr_pair_list_t *out, + fr_dict_attr_t const *parent, + uint8_t const *data, size_t const data_len, void *decode_ctx); + /** Handle arrays of DNS labels for fr_struct_from_network() * */ @@ -51,14 +56,13 @@ static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, uint8_t const *data, size_t const data_len, void *decode_ctx) { -#if 0 /* * @todo - we might need to limit this to only one DNS label. */ - if ((parent->type == FR_TYPE_STRING) && !parent->flags.extra && parent->flags.subtype) { + if ((parent->type == FR_TYPE_STRING) && !parent->flags.extra && + (parent->flags.subtype == FLAG_ENCODE_DNS_LABEL)) { return decode_dns_labels(ctx, out, parent, data, data_len, decode_ctx); } -#endif if (parent->flags.array) return decode_array(ctx, out, parent, data, data_len, decode_ctx); @@ -429,6 +433,75 @@ static ssize_t decode_array(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t return data_len; } +static ssize_t decode_dns_labels(TALLOC_CTX *ctx, fr_pair_list_t *out, + fr_dict_attr_t const *parent, + uint8_t const *data, size_t const data_len, void *decode_ctx) +{ + ssize_t slen; + size_t total, labels_len; + fr_pair_t *vp; + uint8_t const *next = data; + + FR_PROTO_HEX_DUMP(data, data_len, "decode_dns_labels"); + + /* + * This function handles both single-valued and array + * types. It's just easier that way. + */ + if (!parent->flags.array) { + slen = fr_dns_label_uncompressed_length(data, data, data_len, &next, NULL); + if (slen <= 0) goto raw; + + /* + * If the DNS label doesn't exactly fill the option, it's an error. + * + * @todo - we may want to remove this check. + */ + if (next != (data + data_len)) goto raw; + + labels_len = next - data; /* decode only what we've found */ + } else { + /* + * Get the length of the entire set of labels, up + * to (and including) the final 0x00. + * + * If any of the labels point outside of this + * area, OR they are otherwise invalid, then that's an error. + */ + slen = fr_dns_labels_network_verify(data, data, data_len, data, NULL); + if (slen < 0) { + raw: + return decode_raw(ctx, out, parent, data, data_len, decode_ctx); + } + + labels_len = slen; + } + + /* + * Loop over the input buffer, decoding the labels one by + * one. + */ + for (total = 0; total < labels_len; total += slen) { + vp = fr_pair_afrom_da(ctx, parent); + if (!vp) return PAIR_DECODE_OOM; + + /* + * Having verified the input above, this next + * function should never fail unless there's a + * bug in the code. + */ + slen = fr_dns_label_to_value_box(vp, &vp->data, data, labels_len, data + total, true, NULL); + if (slen <= 0) { + talloc_free(vp); + goto raw; + } + + fr_pair_append(out, vp); + } + + return labels_len; +} + /* * One VSA option may contain multiple vendors, each vendor * may contain one or more sub-options. @@ -566,29 +639,29 @@ static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, } FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name); -#if 0 - if ((da->type == FR_TYPE_STRING) && !da->flags.extra && da->flags.subtype) { + if ((da->type == FR_TYPE_STRING) && !da->flags.extra && + (da->flags.subtype == FLAG_ENCODE_DNS_LABEL)) { fr_pair_list_t tmp; fr_pair_list_init(&tmp); slen = decode_dns_labels(ctx, &tmp, da, data + 2, len, decode_ctx); - if (slen < 0) goto raw; + if (slen < 0) { + raw: + fr_pair_list_free(&tmp); + slen = decode_raw(ctx, out, da, data + 2, len, decode_ctx); + if (slen < 0) return slen; + } /* * The DNS labels may only partially fill the * option. If so, then it's a decode error. */ - if ((size_t) slen != len) { - fr_pair_list_free(&tmp); - goto raw; - } + if ((size_t) slen != len) goto raw; fr_pair_list_append(out, &tmp); - } else -#endif - if (da->flags.array) { + } else if (da->flags.array) { slen = decode_array(ctx, out, da, data + 2, len, decode_ctx); } else if (da->type == FR_TYPE_VSA) { diff --git a/src/protocols/dhcpv4/encode.c b/src/protocols/dhcpv4/encode.c index e168768c9f8..2fb49a8565f 100644 --- a/src/protocols/dhcpv4/encode.c +++ b/src/protocols/dhcpv4/encode.c @@ -28,6 +28,8 @@ #include #include #include +#include + #include "dhcpv4.h" #include "attrs.h" @@ -123,11 +125,30 @@ static ssize_t encode_value(fr_dbuff_t *dbuff, case FR_TYPE_BOOL: break; + case FR_TYPE_STRING: + /* + * DNS labels get a special encoder. DNS labels + * MUST NOT be compressed in DHCP. + * + * https://tools.ietf.org/html/rfc8415#section-10 + */ + if (!da->flags.extra && (da->flags.subtype == FLAG_ENCODE_DNS_LABEL)) { + fr_dbuff_marker_t last_byte, src; + + fr_dbuff_marker(&last_byte, &work_dbuff); + fr_dbuff_marker(&src, &work_dbuff); + slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data, NULL); + if (slen < 0) return slen; + break; + } + FALL_THROUGH; + default: slen = fr_value_box_to_network(&work_dbuff, &vp->data); if (slen < 0) return slen; break; } + vp = fr_dcursor_next(cursor); /* We encoded a leaf, advance the cursor */ fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL); @@ -152,22 +173,18 @@ static ssize_t encode_array(fr_dbuff_t *dbuff, "%s: Internal sanity check failed, attribute \"%s\" does not have array bit set", __FUNCTION__, da->name)) return PAIR_ENCODE_FATAL_ERROR; -#if 0 /* * DNS labels have internalized length, so we don't need * length headers. - * - * @todo - not yet functional for DHCPv4/ */ - if ((da->type == FR_TYPE_STRING) && !da->flags.extra && da->flags.subtype){ + if ((da->type == FR_TYPE_STRING) && !da->flags.extra && (da->flags.subtype == FLAG_ENCODE_DNS_LABEL)) { while (fr_dbuff_extend(&work_dbuff)) { vp = fr_dcursor_current(cursor); /* - * DNS labels get a special encoder. DNS labels - * MUST NOT be compressed in DHCP. - * - * https://tools.ietf.org/html/rfc8415#section-10 + * DNS labels get a special encoder. DNS labels are generally not compressed in + * DHCPv4. But sometimes are compressed. Based on whatever the RFC author + * decided was a good idea that day. */ slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data, NULL); if (slen <= 0) return PAIR_ENCODE_FATAL_ERROR; @@ -178,7 +195,6 @@ static ssize_t encode_array(fr_dbuff_t *dbuff, return fr_dbuff_set(dbuff, &work_dbuff); } -#endif while (fr_dbuff_extend(&work_dbuff)) { bool len_field = false;