]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
make encode_vsio() support flat or nested attributes
authorAlan T. DeKok <aland@freeradius.org>
Mon, 10 Jul 2023 18:51:03 +0000 (14:51 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Mon, 10 Jul 2023 19:55:08 +0000 (15:55 -0400)
src/protocols/dhcpv4/encode.c

index e733b836befbd6a159c99022d795a747a0e22fe7..2e6ee39188cb26061c3508206174542a3bb0b361 100644 (file)
@@ -642,6 +642,16 @@ static ssize_t encode_vsio_data(fr_dbuff_t *dbuff,
         */
        da = da_stack->da[depth];
 
+       /*
+        *      RFC 3925 Section 4 says:
+        *
+        *      Multiple instances of this option may be present and MUST be concatenated in accordance with
+        *      RFC 3396.
+        *
+        *      @todo - we don't currently allow encoding more data as per extend_option() or encode_tlv().
+        *      We probably want to do that.  We probably also want to update the decoder so that it
+        *      concatenates options before decoding, too.
+        */
        while (true) {
                len = encode_child(&work_dbuff, da_stack, depth, cursor, encode_ctx);
                if (len == 0) break; /* insufficient space */
@@ -673,12 +683,12 @@ static ssize_t encode_vsio(fr_dbuff_t *dbuff,
                               fr_dcursor_t *cursor, void *encode_ctx)
 {
        fr_dict_attr_t const    *da = da_stack->da[depth];
+       fr_pair_t               *vp;
+       fr_dcursor_t            vendor_cursor;
+       fr_dbuff_t              work_dbuff;
 
        FR_PROTO_STACK_PRINT(da_stack, depth);
 
-       fr_assert(da_stack->da[depth + 1] != NULL);
-       fr_assert(da_stack->da[depth + 2] != NULL);
-
        /*
         *      DA should be a VSA type with the value of OPTION_VENDOR_OPTS.
         */
@@ -688,7 +698,61 @@ static ssize_t encode_vsio(fr_dbuff_t *dbuff,
                return PAIR_ENCODE_FATAL_ERROR;
        }
 
-       return encode_vsio_data(dbuff, da_stack, depth + 2, cursor, encode_ctx);
+       /*
+        *      We go here via a flat attribute, so the da_stack has a
+        *      vendor next, followed by the vendor attributes.
+        */
+       if (da_stack->da[depth + 1]) {
+               fr_assert(da_stack->da[depth + 2] != NULL);
+
+               return encode_vsio_data(dbuff, da_stack, depth + 2, cursor, encode_ctx);
+       }
+
+       vp = fr_dcursor_current(cursor);
+       fr_assert(vp->da == da);
+
+       work_dbuff = FR_DBUFF(dbuff);
+       fr_pair_dcursor_init(&vendor_cursor, &vp->vp_group);
+
+       /*
+        *      Loop over all vendors, and inside of that, loop over all VSA attributes.
+        */
+       while ((vp = fr_dcursor_current(&vendor_cursor)) != NULL) {
+               ssize_t len;
+               fr_dcursor_t vsa_cursor;
+
+               if (vp->da->type != FR_TYPE_VENDOR) continue;
+
+               fr_pair_dcursor_init(&vsa_cursor, &vp->vp_group);
+
+               if ((vp = fr_dcursor_current(&vsa_cursor)) != NULL) {
+                       /*
+                        *      RFC 3925 Section 4 says:
+                        *
+                        *      "An Enterprise Number SHOULD only occur once
+                        *      among all instances of this option.  Behavior
+                        *      is undefined if an Enterprise Number occurs
+                        *      multiple times."
+                        *
+                        *      The function encode_vsio_data() builds
+                        *      one header, and then loops over all
+                        *      children of the vsa_cursor.
+                        */
+                       fr_proto_da_stack_build(da_stack, vp->da);
+                       len = encode_vsio_data(&work_dbuff, da_stack, depth + 2, &vsa_cursor, encode_ctx);
+                       if (len <= 0) return len;
+               }
+
+               vp = fr_dcursor_next(&vendor_cursor);
+       }
+
+       /*
+        *      Skip over the attribute we just encoded.
+        */
+       vp = fr_dcursor_next(cursor);
+       fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
+
+       return fr_dbuff_set(dbuff, &work_dbuff);
 }
 
 /** Encode a DHCP option and any sub-options.