From: Arran Cudbard-Bell Date: Thu, 28 Nov 2024 02:24:08 +0000 (-0600) Subject: Break out more parsing functions X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e2fcc567e06fe986b5c615724d89a5bb3a88a2e8;p=thirdparty%2Ffreeradius-server.git Break out more parsing functions --- diff --git a/src/lib/util/dict_tokenize.c b/src/lib/util/dict_tokenize.c index c105f83f960..ba1582748e4 100644 --- a/src/lib/util/dict_tokenize.c +++ b/src/lib/util/dict_tokenize.c @@ -682,6 +682,16 @@ static fr_dict_attr_t const *dict_gctx_unwind(dict_tokenize_ctx_t *ctx) return ctx->stack[ctx->stack_depth].da; } +static int dict_finalise(dict_tokenize_ctx_t *ctx) +{ + if (dict_fixup_apply(&ctx->fixup) < 0) return -1; + + ctx->value_attr = NULL; + ctx->relative_attr = NULL; + + return 0; +} + static inline CC_HINT(always_inline) void dict_attr_location_set(dict_tokenize_ctx_t *dctx, fr_dict_attr_t *da) { @@ -1246,6 +1256,245 @@ static int dict_read_process_attribute(dict_tokenize_ctx_t *ctx, char **argv, in return 0; } +static int dict_read_process_begin_protocol(dict_tokenize_ctx_t *dctx, char **argv, int argc, + UNUSED fr_dict_attr_flags_t *base_flags) +{ + fr_dict_t *found; + dict_tokenize_frame_t const *frame; + + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + + if (argc != 1) { + fr_strerror_const_push("Invalid BEGIN-PROTOCOL entry"); + error: + return -1; + } + + /* + * If we're not parsing in the context of the internal + * dictionary, then we don't allow BEGIN-PROTOCOL + * statements. + */ + if (dctx->dict != dict_gctx->internal) { + fr_strerror_const_push("Nested BEGIN-PROTOCOL statements are not allowed"); + goto error; + } + + found = dict_by_protocol_name(argv[0]); + if (!found) { + fr_strerror_printf("Unknown protocol '%s'", argv[0]); + goto error; + } + + frame = dict_gctx_find_frame(dctx, NEST_PROTOCOL); + if (frame) { + fr_strerror_printf_push("Nested BEGIN-PROTOCOL is forbidden. Previous definition is at %s[%d]", + frame->filename, frame->line); + goto error; + } + + /* + * Add a temporary fixup pool + * + * @todo - make a nested ctx? + */ + dict_fixup_init(NULL, &dctx->fixup); + + /* + * We're in the middle of loading this dictionary. Tell + * fr_dict_protocol_afrom_file() to suppress recursive references. + */ + found->loading = true; + + dctx->dict = found; + + if (dict_gctx_push(dctx, dctx->dict->root) < 0) goto error; + dctx->stack[dctx->stack_depth].nest = NEST_PROTOCOL; + + return 0; +} + +static int dict_read_process_begin_tlv(dict_tokenize_ctx_t *dctx, char **argv, int argc, + UNUSED fr_dict_attr_flags_t *base_flags) +{ + fr_dict_attr_t const *da; + fr_dict_attr_t const *common; + + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + + if (argc != 1) { + fr_strerror_const_push("Invalid BEGIN-TLV entry"); + error: + return -1; + } + + da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, argv[0]); + if (!da) { + fr_strerror_const_push("Failed resolving attribute in BEGIN-TLV entry"); + goto error; + } + + if (da->type != FR_TYPE_TLV) { + fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'", + argv[0], + fr_type_to_str(da->type)); + goto error; + } + + common = fr_dict_attr_common_parent(dctx->stack[dctx->stack_depth].da, da, true); + if (!common || + (common->type == FR_TYPE_VSA)) { + fr_strerror_printf_push("Attribute '%s' should be a child of '%s'", + argv[0], dctx->stack[dctx->stack_depth].da->name); + goto error; + } + + if (dict_gctx_push(dctx, da) < 0) goto error; + dctx->stack[dctx->stack_depth].nest = NEST_TLV; + + return 0; +} + +static int dict_read_process_begin_vendor(dict_tokenize_ctx_t *dctx, char **argv, int argc, + UNUSED fr_dict_attr_flags_t *base_flags) +{ + fr_dict_vendor_t const *vendor; + fr_dict_attr_flags_t flags; + + fr_dict_attr_t const *vsa_da; + fr_dict_attr_t const *vendor_da; + fr_dict_attr_t *new; + dict_tokenize_frame_t const *frame; + char *p; + + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + + if (argc < 1) { + fr_strerror_const_push("Invalid BEGIN-VENDOR entry"); + error: + return 01; + } + + vendor = fr_dict_vendor_by_name(dctx->dict, argv[0]); + if (!vendor) { + fr_strerror_printf_push("Unknown vendor '%s'", argv[0]); + goto error; + } + + /* + * Check for extended attr VSAs + * + * BEGIN-VENDOR foo parent=Foo-Encapsulation-Attr + */ + if (argc > 1) { + fr_dict_attr_t const *da; + + if (strncmp(argv[1], "parent=", 7) != 0) { + fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[1]); + goto error; + } + + p = argv[1] + 7; + da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, p); + if (!da) { + fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[1]); + goto error; + } + + if (da->type != FR_TYPE_VSA) { + fr_strerror_printf_push("Invalid parent for BEGIN-VENDOR. " + "Attribute '%s' should be 'vsa' but is '%s'", p, + fr_type_to_str(da->type)); + goto error; + } + + vsa_da = da; + + } else if (dctx->dict->vsa_parent) { + /* + * Check that the protocol-specific VSA parent exists. + */ + vsa_da = dict_attr_child_by_num(dctx->stack[dctx->stack_depth].da, dctx->dict->vsa_parent); + if (!vsa_da) { + fr_strerror_printf_push("Failed finding VSA parent for Vendor %s", + vendor->name); + goto error; + } + + } else if (dctx->dict->string_based) { + vsa_da = dctx->dict->root; + + } else { + fr_strerror_printf_push("BEGIN-VENDOR is forbidden for protocol %s - it has no ATTRIBUTE of type 'vsa'", + dctx->dict->root->name); + goto error; + } + + frame = dict_gctx_find_frame(dctx, NEST_VENDOR); + if (frame) { + fr_strerror_printf_push("Nested BEGIN-VENDOR is forbidden. Previous definition is at %s[%d]", + frame->filename, frame->line); + goto error; + } + + /* + * Create a VENDOR attribute on the fly, either in the context + * of the VSA (26) attribute. + */ + vendor_da = dict_attr_child_by_num(vsa_da, vendor->pen); + if (!vendor_da) { + memset(&flags, 0, sizeof(flags)); + + flags.type_size = dctx->dict->proto->default_type_size; + flags.length = dctx->dict->proto->default_type_length; + + /* + * See if this vendor has + * specific sizes for type / + * length. + * + * @todo - Make this more protocol agnostic! + */ + if ((vsa_da->type == FR_TYPE_VSA) && + (vsa_da->parent->flags.is_root)) { + fr_dict_vendor_t const *dv; + + dv = fr_dict_vendor_by_num(dctx->dict, vendor->pen); + if (dv) { + flags.type_size = dv->type; + flags.length = dv->length; + } + } + + new = dict_attr_alloc(dctx->dict->pool, + vsa_da, argv[0], vendor->pen, FR_TYPE_VENDOR, + &(dict_attr_args_t){ .flags = &flags }); + if (unlikely(!new)) goto error; + + if (dict_attr_child_add(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) { + talloc_free(new); + goto error; + } + + if (dict_attr_add_to_namespace(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) { + talloc_free(new); + goto error; + } + + vendor_da = new; + } else { + fr_assert(vendor_da->type == FR_TYPE_VENDOR); + } + + if (dict_gctx_push(dctx, vendor_da) < 0) goto error; + dctx->stack[dctx->stack_depth].nest = NEST_VENDOR; + + return 0; +} + /* * Process the DEFINE command * @@ -1364,11 +1613,129 @@ static int dict_read_process_define(dict_tokenize_ctx_t *ctx, char **argv, int a return 0; } +static int dict_read_process_end_protocol(dict_tokenize_ctx_t *dctx, char **argv, int argc, + UNUSED fr_dict_attr_flags_t *base_flags) +{ + fr_dict_t const *found; + + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + + if (argc != 1) { + fr_strerror_const("Invalid END-PROTOCOL entry"); + error: + return -1; + } + + found = dict_by_protocol_name(argv[0]); + if (!found) { + fr_strerror_printf("END-PROTOCOL %s does not refer to a valid protocol", argv[0]); + goto error; + } + + if (found != dctx->dict) { + fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s", + argv[0], found->root->name); + goto error; + } + + /* + * Pop the stack until we get to a PROTOCOL nesting. + */ + while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_PROTOCOL)) { + if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) { + fr_strerror_printf_push("END-PROTOCOL %s with mismatched BEGIN-??? %s", argv[0], + dctx->stack[dctx->stack_depth].da->name); + goto error; + } + + dctx->stack_depth--; + } + + if (dctx->stack_depth == 0) { + fr_strerror_printf_push("END-PROTOCOL %s with no previous BEGIN-PROTOCOL", argv[0]); + goto error; + } + + if (found->root != dctx->stack[dctx->stack_depth].da) { + fr_strerror_printf_push("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s", argv[0], + dctx->stack[dctx->stack_depth].da->name); + goto error; + } + + /* + * Applies fixups to any attributes added + * to the protocol dictionary. Note that + * the finalise function prints out the + * original filename / line of the + * error. So we don't need to do that + * here. + */ + if (dict_finalise(dctx) < 0) goto error; + + dctx->stack_depth--; + dctx->dict = dctx->stack[dctx->stack_depth].dict; + + return 0; +} + +static int dict_read_process_end_tlv(dict_tokenize_ctx_t *dctx, char **argv, int argc, + UNUSED fr_dict_attr_flags_t *base_flags) +{ + fr_dict_attr_t const *da; + + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + + if (argc != 1) { + fr_strerror_const_push("Invalid END-TLV entry"); + error: + return -1; + } + + da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth - 1].da, argv[0]); + if (!da) { + fr_strerror_const_push("Failed resolving attribute in END-TLV entry"); + goto error; + } + + /* + * Pop the stack until we get to a TLV nesting. + */ + while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_TLV)) { + if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) { + fr_strerror_printf_push("END-TLV %s with mismatched BEGIN-??? %s", argv[0], + dctx->stack[dctx->stack_depth].da->name); + goto error; + } + + dctx->stack_depth--; + } + + if (dctx->stack_depth == 0) { + fr_strerror_printf_push("END-TLV %s with no previous BEGIN-TLV", argv[0]); + goto error; + } + + if (da != dctx->stack[dctx->stack_depth].da) { + fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[0], + dctx->stack[dctx->stack_depth].da->name); + goto error; + } + + dctx->stack_depth--; + + return 0; +} + static int dict_read_process_end_vendor(dict_tokenize_ctx_t *dctx, char **argv, int argc, UNUSED fr_dict_attr_flags_t *base_flags) { fr_dict_vendor_t const *vendor; + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + if (argc != 1) { fr_strerror_const_push("Invalid END-VENDOR entry"); error: @@ -1515,7 +1882,8 @@ static int dict_read_process_enum(dict_tokenize_ctx_t *ctx, char **argv, int arg /* * Process the FLAGS command */ -static int dict_read_process_flags(UNUSED dict_tokenize_ctx_t *dctx, char **argv, int argc, fr_dict_attr_flags_t *base_flags) +static int dict_read_process_flags(UNUSED dict_tokenize_ctx_t *dctx, char **argv, int argc, + fr_dict_attr_flags_t *base_flags) { bool sense = true; @@ -1538,232 +1906,32 @@ static int dict_read_process_flags(UNUSED dict_tokenize_ctx_t *dctx, char **argv return -1; } -/* - * Process the MEMBER command +/** Process a STRUCT name attr value + * + * Define struct 'name' when key 'attr' has 'value'. + * + * Which MUST be a sub-structure of another struct */ -static int dict_read_process_member(dict_tokenize_ctx_t *ctx, char **argv, int argc, - fr_dict_attr_flags_t const *base_flags) +static int dict_read_process_struct(dict_tokenize_ctx_t *ctx, char **argv, int argc, + UNUSED fr_dict_attr_flags_t *base_flags) { - fr_dict_attr_t *da; - - if ((argc < 2) || (argc > 3)) { - fr_strerror_const("Invalid MEMBER syntax"); - return -1; - } - - if (ctx->stack[ctx->stack_depth].da->type != FR_TYPE_STRUCT) { - fr_strerror_const("MEMBER can only be used for ATTRIBUTEs of type 'struct'"); - return -1; - } + fr_value_box_t value = FR_VALUE_BOX_INITIALISER_NULL(value); + int i; + fr_dict_attr_t const *parent = NULL; + unsigned int attr; + char *key_attr = argv[1]; + char *name = argv[0]; + fr_dict_attr_t *da; - if (dict_read_process_common(ctx, &da, argv[0], argv[1], - (argc > 2) ? argv[2] : NULL, base_flags) < 0) { + if ((argc < 3) || (argc > 4)) { + fr_strerror_const("Invalid STRUCT syntax"); return -1; } -#ifdef STATIC_ANALYZER - if (!ctx->dict) goto error; -#endif + fr_assert(ctx->stack_depth > 0); /* - * If our parent is a fixed-size struct, then we have to be fixed-size, too. - */ - da->flags.is_known_width |= ctx->stack[ctx->stack_depth].da->flags.is_known_width; - - /* - * Double check bit field magic - */ - if (ctx->stack[ctx->stack_depth].member_num > 0) { - fr_dict_attr_t const *previous; - - previous = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, - ctx->stack[ctx->stack_depth].member_num); - /* - * Check that the previous bit field ended on a - * byte boundary. - * - * Note that the previous attribute might be a deferred TLV, in which case it doesn't - * exist. That's fine. - */ - if (previous && previous->flags.extra && (previous->flags.subtype == FLAG_BIT_FIELD)) { - /* - * This attribute is a bit field. Keep - * track of where in the byte we are - * located. - */ - if (da->flags.extra && (da->flags.subtype == FLAG_BIT_FIELD)) { - da->flags.flag_byte_offset = (da->flags.length + previous->flags.flag_byte_offset) & 0x07; - - } else { - if (previous->flags.flag_byte_offset != 0) { - fr_strerror_printf("Previous bitfield %s did not end on a byte boundary", - previous->name); - error: - talloc_free(da); - return -1; - } - } - } - } - - /* - * Check if the parent 'struct' is fixed size. And if - * so, complain if we're adding a variable sized member. - */ - if (ctx->stack[ctx->stack_depth].struct_is_closed) { - fr_strerror_printf("Cannot add MEMBER to 'struct' %s after a variable sized member %s", - ctx->stack[ctx->stack_depth].da->name, - ctx->stack[ctx->stack_depth].struct_is_closed->name); - goto error; - } - - /* - * Ensure that no previous child has "key" or "length" set. - */ - if (da->type == FR_TYPE_TLV) { - fr_dict_attr_t const *key; - int i; - - /* - * @todo - cache the key field in the stack frame, so we don't have to loop over the children. - */ - for (i = 1; i <= ctx->stack[ctx->stack_depth].member_num; i++) { - key = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, i); - if (!key) continue; /* really should be WTF? */ - - if (fr_dict_attr_is_key_field(key)) { - fr_strerror_printf("'struct' %s has a 'key' field %s, and cannot end with a TLV %s", - ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]); - goto error; - } - - if (da_is_length_field(key)) { - fr_strerror_printf("'struct' %s has a 'length' field %s, and cannot end with a TLV %s", - ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]); - goto error; - } - } - } - - if (unlikely(dict_attr_parent_init(&da, ctx->stack[ctx->stack_depth].da) < 0)) goto error; - if (unlikely(dict_attr_num_init(da, ++ctx->stack[ctx->stack_depth].member_num) < 0)) goto error; - if (unlikely(dict_attr_finalise(&da, argv[0]) < 0)) goto error; - - /* - * Check to see if this is a duplicate attribute - * and whether we should ignore it or error out... - */ - switch (dict_attr_allow_dup(da)) { - case 1: - break; - - case 0: - talloc_free(da); - return 0; - - default: - goto error; - } - - switch (dict_attr_add_or_fixup(&ctx->fixup, &da)) { - default: - goto error; - - /* New attribute, fixup stack */ - case 0: - /* - * A 'struct' can have a MEMBER of type 'tlv', but ONLY - * as the last entry in the 'struct'. If we see that, - * set the previous attribute to the TLV we just added. - * This allows the children of the TLV to be parsed as - * partial OIDs, so we don't need to know the full path - * to them. - */ - if (da->type == FR_TYPE_TLV) { - ctx->relative_attr = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, - ctx->stack[ctx->stack_depth].member_num); - if (ctx->relative_attr && (dict_gctx_push(ctx, ctx->relative_attr) < 0)) return -1; - return 0; - - } - - /* - * Add the size of this member to the parent struct. - */ - if (ctx->stack[ctx->stack_depth].da->flags.length) { - /* - * Fixed-size struct can't have MEMBERs of unknown sizes. - */ - if (!da->flags.is_known_width) { - fr_strerror_printf("'struct' %s has fixed size %u, but member %s is of unknown size", - ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length, - argv[0]); - return -1; - } - - ctx->stack[ctx->stack_depth].struct_size += da->flags.length; - - } - - /* - * Check for overflow. - */ - if (ctx->stack[ctx->stack_depth].da->flags.length && - (ctx->stack[ctx->stack_depth].struct_size > ctx->stack[ctx->stack_depth].da->flags.length)) { - fr_strerror_printf("'struct' %s has fixed size %u, but member %s overflows that length", - ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length, - argv[0]); - return -1; - } - - if (dict_set_value_attr(ctx, da) < 0) return -1; - - /* - * Check if this MEMBER closes the structure. - * - * @todo - close this struct if the child struct is variable sized. For now, it - * looks like most child structs are at the end of the parent. - * - * The solution is to update the unwind() function to check if the da we've - * unwound to is a struct, and then if so... get the last child, and mark it - * closed. - */ - if (!da->flags.is_known_width) ctx->stack[ctx->stack_depth].struct_is_closed = da; - break; - - /* Deferred attribute, don't begin the TLV section automatically */ - case 1: - break; - } - - return 0; -} - -/** Process a STRUCT name attr value - * - * Define struct 'name' when key 'attr' has 'value'. - * - * Which MUST be a sub-structure of another struct - */ -static int dict_read_process_struct(dict_tokenize_ctx_t *ctx, char **argv, int argc) -{ - fr_value_box_t value = FR_VALUE_BOX_INITIALISER_NULL(value); - int i; - fr_dict_attr_t const *parent = NULL; - unsigned int attr; - char *key_attr = argv[1]; - char *name = argv[0]; - fr_dict_attr_t *da; - - if ((argc < 3) || (argc > 4)) { - fr_strerror_const("Invalid STRUCT syntax"); - return -1; - } - - fr_assert(ctx->stack_depth > 0); - - /* - * Unwind the stack until we find a parent which has a child named "key_attr" + * Unwind the stack until we find a parent which has a child named "key_attr" */ for (i = ctx->stack_depth; i > 0; i--) { parent = dict_attr_by_name(NULL, ctx->stack[i].da, key_attr); @@ -1875,28 +2043,229 @@ static int dict_read_process_struct(dict_tokenize_ctx_t *ctx, char **argv, int a default: goto error; - /* FIXME: Should dict_attr_enum_add_name also be called in the fixup code? */ + /* FIXME: Should dict_attr_enum_add_name also be called in the fixup code? */ + case 0: + da = dict_attr_by_name(NULL, parent, name); + if (!da) return -1; + + /* + * A STRUCT definition is an implicit BEGIN-STRUCT. + */ + ctx->relative_attr = NULL; + if (dict_gctx_push(ctx, da) < 0) return -1; + + /* + * Add the VALUE to the parent attribute, and ensure that + * the VALUE also contains a pointer to the child struct. + */ + if (dict_attr_enum_add_name(fr_dict_attr_unconst(parent), name, &value, false, true, da) < 0) { + fr_value_box_clear(&value); + return -1; + } + fr_value_box_clear(&value); + break; + + case 1: + break; + } + + return 0; +} + +/* + * Process the MEMBER command + */ +static int dict_read_process_member(dict_tokenize_ctx_t *ctx, char **argv, int argc, + fr_dict_attr_flags_t const *base_flags) +{ + fr_dict_attr_t *da; + + if ((argc < 2) || (argc > 3)) { + fr_strerror_const("Invalid MEMBER syntax"); + return -1; + } + + if (ctx->stack[ctx->stack_depth].da->type != FR_TYPE_STRUCT) { + fr_strerror_const("MEMBER can only be used for ATTRIBUTEs of type 'struct'"); + return -1; + } + + if (dict_read_process_common(ctx, &da, argv[0], argv[1], + (argc > 2) ? argv[2] : NULL, base_flags) < 0) { + return -1; + } + +#ifdef STATIC_ANALYZER + if (!ctx->dict) goto error; +#endif + + /* + * If our parent is a fixed-size struct, then we have to be fixed-size, too. + */ + da->flags.is_known_width |= ctx->stack[ctx->stack_depth].da->flags.is_known_width; + + /* + * Double check bit field magic + */ + if (ctx->stack[ctx->stack_depth].member_num > 0) { + fr_dict_attr_t const *previous; + + previous = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, + ctx->stack[ctx->stack_depth].member_num); + /* + * Check that the previous bit field ended on a + * byte boundary. + * + * Note that the previous attribute might be a deferred TLV, in which case it doesn't + * exist. That's fine. + */ + if (previous && previous->flags.extra && (previous->flags.subtype == FLAG_BIT_FIELD)) { + /* + * This attribute is a bit field. Keep + * track of where in the byte we are + * located. + */ + if (da->flags.extra && (da->flags.subtype == FLAG_BIT_FIELD)) { + da->flags.flag_byte_offset = (da->flags.length + previous->flags.flag_byte_offset) & 0x07; + + } else { + if (previous->flags.flag_byte_offset != 0) { + fr_strerror_printf("Previous bitfield %s did not end on a byte boundary", + previous->name); + error: + talloc_free(da); + return -1; + } + } + } + } + + /* + * Check if the parent 'struct' is fixed size. And if + * so, complain if we're adding a variable sized member. + */ + if (ctx->stack[ctx->stack_depth].struct_is_closed) { + fr_strerror_printf("Cannot add MEMBER to 'struct' %s after a variable sized member %s", + ctx->stack[ctx->stack_depth].da->name, + ctx->stack[ctx->stack_depth].struct_is_closed->name); + goto error; + } + + /* + * Ensure that no previous child has "key" or "length" set. + */ + if (da->type == FR_TYPE_TLV) { + fr_dict_attr_t const *key; + int i; + + /* + * @todo - cache the key field in the stack frame, so we don't have to loop over the children. + */ + for (i = 1; i <= ctx->stack[ctx->stack_depth].member_num; i++) { + key = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, i); + if (!key) continue; /* really should be WTF? */ + + if (fr_dict_attr_is_key_field(key)) { + fr_strerror_printf("'struct' %s has a 'key' field %s, and cannot end with a TLV %s", + ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]); + goto error; + } + + if (da_is_length_field(key)) { + fr_strerror_printf("'struct' %s has a 'length' field %s, and cannot end with a TLV %s", + ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]); + goto error; + } + } + } + + if (unlikely(dict_attr_parent_init(&da, ctx->stack[ctx->stack_depth].da) < 0)) goto error; + if (unlikely(dict_attr_num_init(da, ++ctx->stack[ctx->stack_depth].member_num) < 0)) goto error; + if (unlikely(dict_attr_finalise(&da, argv[0]) < 0)) goto error; + + /* + * Check to see if this is a duplicate attribute + * and whether we should ignore it or error out... + */ + switch (dict_attr_allow_dup(da)) { + case 1: + break; + + case 0: + talloc_free(da); + return 0; + + default: + goto error; + } + + switch (dict_attr_add_or_fixup(&ctx->fixup, &da)) { + default: + goto error; + + /* New attribute, fixup stack */ case 0: - da = dict_attr_by_name(NULL, parent, name); - if (!da) return -1; + /* + * A 'struct' can have a MEMBER of type 'tlv', but ONLY + * as the last entry in the 'struct'. If we see that, + * set the previous attribute to the TLV we just added. + * This allows the children of the TLV to be parsed as + * partial OIDs, so we don't need to know the full path + * to them. + */ + if (da->type == FR_TYPE_TLV) { + ctx->relative_attr = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, + ctx->stack[ctx->stack_depth].member_num); + if (ctx->relative_attr && (dict_gctx_push(ctx, ctx->relative_attr) < 0)) return -1; + return 0; + + } /* - * A STRUCT definition is an implicit BEGIN-STRUCT. + * Add the size of this member to the parent struct. */ - ctx->relative_attr = NULL; - if (dict_gctx_push(ctx, da) < 0) return -1; + if (ctx->stack[ctx->stack_depth].da->flags.length) { + /* + * Fixed-size struct can't have MEMBERs of unknown sizes. + */ + if (!da->flags.is_known_width) { + fr_strerror_printf("'struct' %s has fixed size %u, but member %s is of unknown size", + ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length, + argv[0]); + return -1; + } + + ctx->stack[ctx->stack_depth].struct_size += da->flags.length; + + } /* - * Add the VALUE to the parent attribute, and ensure that - * the VALUE also contains a pointer to the child struct. - */ - if (dict_attr_enum_add_name(fr_dict_attr_unconst(parent), name, &value, false, true, da) < 0) { - fr_value_box_clear(&value); + * Check for overflow. + */ + if (ctx->stack[ctx->stack_depth].da->flags.length && + (ctx->stack[ctx->stack_depth].struct_size > ctx->stack[ctx->stack_depth].da->flags.length)) { + fr_strerror_printf("'struct' %s has fixed size %u, but member %s overflows that length", + ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length, + argv[0]); return -1; } - fr_value_box_clear(&value); + + if (dict_set_value_attr(ctx, da) < 0) return -1; + + /* + * Check if this MEMBER closes the structure. + * + * @todo - close this struct if the child struct is variable sized. For now, it + * looks like most child structs are at the end of the parent. + * + * The solution is to update the unwind() function to check if the da we've + * unwound to is a struct, and then if so... get the last child, and mark it + * closed. + */ + if (!da->flags.is_known_width) ctx->stack[ctx->stack_depth].struct_is_closed = da; break; + /* Deferred attribute, don't begin the TLV section automatically */ case 1: break; } @@ -2010,6 +2379,9 @@ static int dict_read_process_vendor(dict_tokenize_ctx_t *dctx, char **argv, int fr_dict_vendor_t *mutable; fr_dict_t *dict = dctx->dict; + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + if ((argc < 2) || (argc > 3)) { fr_strerror_const("Invalid VENDOR syntax"); return -1; @@ -2023,9 +2395,6 @@ static int dict_read_process_vendor(dict_tokenize_ctx_t *dctx, char **argv, int return -1; } - dctx->value_attr = NULL; - dctx->relative_attr = NULL; - /* * Look for a format statement. Allow it to over-ride the hard-coded formats below. */ @@ -2057,7 +2426,7 @@ static int dict_read_process_vendor(dict_tokenize_ctx_t *dctx, char **argv, int * * Allows vendor and TLV context to persist across $INCLUDEs */ -static int dict_read_process_protocol(char **argv, int argc) +static int dict_read_process_protocol(dict_tokenize_ctx_t *dctx, char **argv, int argc, UNUSED fr_dict_attr_flags_t *base_flag) { unsigned int value; unsigned int type_size = 0; @@ -2066,6 +2435,9 @@ static int dict_read_process_protocol(char **argv, int argc) bool require_dl = false; bool string_based = false; + dctx->value_attr = NULL; + dctx->relative_attr = NULL; + if ((argc < 2) || (argc > 3)) { fr_strerror_const("Missing arguments after PROTOCOL. Expected PROTOCOL "); return -1; @@ -2197,16 +2569,6 @@ post_option: return 0; } -static int dict_finalise(dict_tokenize_ctx_t *ctx) -{ - if (dict_fixup_apply(&ctx->fixup) < 0) return -1; - - ctx->value_attr = NULL; - ctx->relative_attr = NULL; - - return 0; -} - /** Maintain a linked list of filenames which we've seen loading this dictionary * * This is used for debug messages, so we have a copy of the original file path @@ -2294,10 +2656,17 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx, static fr_dict_keyword_t const keywords[] = { { L("ALIAS"), { dict_read_process_alias } }, { L("ATTRIBUTE"), { dict_read_process_attribute } }, + { L("BEGIN-PROTOCOL"), { dict_read_process_begin_protocol } }, + { L("BEGIN-TLV"), { dict_read_process_begin_tlv } }, + { L("BEGIN-VENDOR"), { dict_read_process_begin_vendor } }, { L("DEFINE"), { dict_read_process_define } }, + { L("END-PROTOCOL"), { dict_read_process_end_protocol } }, + { L("END-TLV"), { dict_read_process_end_tlv } }, { L("END-VENDOR"), { dict_read_process_end_vendor } }, { L("ENUM"), { dict_read_process_enum } }, { L("FLAGS"), { dict_read_process_flags } }, + { L("PROTOCOL"), { dict_read_process_protocol }}, + { L("STRUCT"), { dict_read_process_struct } }, { L("VALUE"), { dict_read_process_value } }, { L("VENDOR"), { dict_read_process_vendor } }, }; @@ -2430,8 +2799,6 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx, while (fgets(buf, sizeof(buf), fp) != NULL) { fr_dict_keyword_parser_t const *parser; - dict_tokenize_frame_t const *frame; - dctx->stack[dctx->stack_depth].line = ++line; @@ -2522,14 +2889,6 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx, was_member = false; } - /* - * Process STRUCT lines. - */ - if (strcasecmp(argv[0], "STRUCT") == 0) { - if (dict_read_process_struct(dctx, argv + 1, argc - 1) == -1) goto error; - continue; - } - /* * See if we need to import another dictionary. */ @@ -2548,348 +2907,6 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx, continue; } /* $INCLUDE */ - /* - * Reset the previous attribute when we see - * VENDOR or PROTOCOL or BEGIN/END-VENDOR, etc. - */ - dctx->value_attr = NULL; - dctx->relative_attr = NULL; - - /* - * Process PROTOCOL line. Defines a new protocol. - */ - if (strcasecmp(argv[0], "PROTOCOL") == 0) { - if (argc < 2) { - fr_strerror_const_push("Invalid PROTOCOL entry"); - goto error; - } - if (dict_read_process_protocol(argv + 1, argc - 1) == -1) goto error; - continue; - } - - /* - * Switches the current protocol context - */ - if (strcasecmp(argv[0], "BEGIN-PROTOCOL") == 0) { - fr_dict_t *found; - - if (argc != 2) { - fr_strerror_const_push("Invalid BEGIN-PROTOCOL entry"); - goto error; - } - - /* - * If we're not parsing in the context of the internal - * dictionary, then we don't allow BEGIN-PROTOCOL - * statements. - */ - if (dctx->dict != dict_gctx->internal) { - fr_strerror_const_push("Nested BEGIN-PROTOCOL statements are not allowed"); - goto error; - } - - found = dict_by_protocol_name(argv[1]); - if (!found) { - fr_strerror_printf("Unknown protocol '%s'", argv[1]); - goto error; - } - - frame = dict_gctx_find_frame(dctx, NEST_PROTOCOL); - if (frame) { - fr_strerror_printf_push("Nested BEGIN-PROTOCOL is forbidden. Previous definition is at %s[%d]", - frame->filename, frame->line); - goto error; - } - - /* - * Add a temporary fixup pool - * - * @todo - make a nested ctx? - */ - dict_fixup_init(NULL, &dctx->fixup); - - /* - * We're in the middle of loading this dictionary. Tell - * fr_dict_protocol_afrom_file() to suppress recursive references. - */ - found->loading = true; - - dctx->dict = found; - - if (dict_gctx_push(dctx, dctx->dict->root) < 0) goto error; - dctx->stack[dctx->stack_depth].nest = NEST_PROTOCOL; - continue; - } - - /* - * Switches back to the previous protocol context - */ - if (strcasecmp(argv[0], "END-PROTOCOL") == 0) { - fr_dict_t const *found; - - if (argc != 2) { - fr_strerror_const("Invalid END-PROTOCOL entry"); - goto error; - } - - found = dict_by_protocol_name(argv[1]); - if (!found) { - fr_strerror_printf("END-PROTOCOL %s does not refer to a valid protocol", argv[1]); - goto error; - } - - if (found != dctx->dict) { - fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s", - argv[1], found->root->name); - goto error; - } - - /* - * Pop the stack until we get to a PROTOCOL nesting. - */ - while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_PROTOCOL)) { - if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) { - fr_strerror_printf_push("END-PROTOCOL %s with mismatched BEGIN-??? %s", argv[1], - dctx->stack[dctx->stack_depth].da->name); - goto error; - } - - dctx->stack_depth--; - } - - if (dctx->stack_depth == 0) { - fr_strerror_printf_push("END-PROTOCOL %s with no previous BEGIN-PROTOCOL", argv[1]); - goto error; - } - - if (found->root != dctx->stack[dctx->stack_depth].da) { - fr_strerror_printf_push("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s", argv[1], - dctx->stack[dctx->stack_depth].da->name); - goto error; - } - - /* - * Applies fixups to any attributes added - * to the protocol dictionary. Note that - * the finalise function prints out the - * original filename / line of the - * error. So we don't need to do that - * here. - */ - if (dict_finalise(dctx) < 0) goto error; - - dctx->stack_depth--; - dctx->dict = dctx->stack[dctx->stack_depth].dict; - continue; - } - - /* - * Switches TLV parent context - */ - if (strcasecmp(argv[0], "BEGIN-TLV") == 0) { - fr_dict_attr_t const *common; - - if (argc != 2) { - fr_strerror_const_push("Invalid BEGIN-TLV entry"); - goto error; - } - - da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, argv[1]); - if (!da) { - fr_strerror_const_push("Failed resolving attribute in BEGIN-TLV entry"); - goto error; - } - - if (da->type != FR_TYPE_TLV) { - fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'", - argv[1], - fr_type_to_str(da->type)); - goto error; - } - - common = fr_dict_attr_common_parent(dctx->stack[dctx->stack_depth].da, da, true); - if (!common || - (common->type == FR_TYPE_VSA)) { - fr_strerror_printf_push("Attribute '%s' should be a child of '%s'", - argv[1], dctx->stack[dctx->stack_depth].da->name); - goto error; - } - - if (dict_gctx_push(dctx, da) < 0) goto error; - dctx->stack[dctx->stack_depth].nest = NEST_TLV; - continue; - } /* BEGIN-TLV */ - - /* - * Switches back to previous TLV parent - */ - if (strcasecmp(argv[0], "END-TLV") == 0) { - if (argc != 2) { - fr_strerror_const_push("Invalid END-TLV entry"); - goto error; - } - - da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth - 1].da, argv[1]); - if (!da) { - fr_strerror_const_push("Failed resolving attribute in END-TLV entry"); - goto error; - } - - /* - * Pop the stack until we get to a TLV nesting. - */ - while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_TLV)) { - if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) { - fr_strerror_printf_push("END-TLV %s with mismatched BEGIN-??? %s", argv[1], - dctx->stack[dctx->stack_depth].da->name); - goto error; - } - - dctx->stack_depth--; - } - - if (dctx->stack_depth == 0) { - fr_strerror_printf_push("END-TLV %s with no previous BEGIN-TLV", argv[1]); - goto error; - } - - if (da != dctx->stack[dctx->stack_depth].da) { - fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[1], - dctx->stack[dctx->stack_depth].da->name); - goto error; - } - - dctx->stack_depth--; - continue; - } /* END-VENDOR */ - - if (strcasecmp(argv[0], "BEGIN-VENDOR") == 0) { - fr_dict_vendor_t const *vendor; - fr_dict_attr_flags_t flags; - - fr_dict_attr_t const *vsa_da; - fr_dict_attr_t const *vendor_da; - fr_dict_attr_t *new; - - if (argc < 2) { - fr_strerror_const_push("Invalid BEGIN-VENDOR entry"); - goto error; - } - - vendor = fr_dict_vendor_by_name(dctx->dict, argv[1]); - if (!vendor) { - fr_strerror_printf_push("Unknown vendor '%s'", argv[1]); - goto error; - } - - /* - * Check for extended attr VSAs - * - * BEGIN-VENDOR foo parent=Foo-Encapsulation-Attr - */ - if (argc > 2) { - if (strncmp(argv[2], "parent=", 7) != 0) { - fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[2]); - goto error; - } - - p = argv[2] + 7; - da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, p); - if (!da) { - fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[2]); - goto error; - } - - if (da->type != FR_TYPE_VSA) { - fr_strerror_printf_push("Invalid parent for BEGIN-VENDOR. " - "Attribute '%s' should be 'vsa' but is '%s'", p, - fr_type_to_str(da->type)); - goto error; - } - - vsa_da = da; - - } else if (dctx->dict->vsa_parent) { - /* - * Check that the protocol-specific VSA parent exists. - */ - vsa_da = dict_attr_child_by_num(dctx->stack[dctx->stack_depth].da, dctx->dict->vsa_parent); - if (!vsa_da) { - fr_strerror_printf_push("Failed finding VSA parent for Vendor %s", - vendor->name); - goto error; - } - - } else if (dctx->dict->string_based) { - vsa_da = dctx->dict->root; - - } else { - fr_strerror_printf_push("BEGIN-VENDOR is forbidden for protocol %s - it has no ATTRIBUTE of type 'vsa'", - dctx->dict->root->name); - goto error; - } - - frame = dict_gctx_find_frame(dctx, NEST_VENDOR); - if (frame) { - fr_strerror_printf_push("Nested BEGIN-VENDOR is forbidden. Previous definition is at %s[%d]", - frame->filename, frame->line); - goto error; - } - - /* - * Create a VENDOR attribute on the fly, either in the context - * of the VSA (26) attribute. - */ - vendor_da = dict_attr_child_by_num(vsa_da, vendor->pen); - if (!vendor_da) { - memset(&flags, 0, sizeof(flags)); - - flags.type_size = dctx->dict->proto->default_type_size; - flags.length = dctx->dict->proto->default_type_length; - - /* - * See if this vendor has - * specific sizes for type / - * length. - * - * @todo - Make this more protocol agnostic! - */ - if ((vsa_da->type == FR_TYPE_VSA) && - (vsa_da->parent->flags.is_root)) { - fr_dict_vendor_t const *dv; - - dv = fr_dict_vendor_by_num(dctx->dict, vendor->pen); - if (dv) { - flags.type_size = dv->type; - flags.length = dv->length; - } - } - - new = dict_attr_alloc(dctx->dict->pool, - vsa_da, argv[1], vendor->pen, FR_TYPE_VENDOR, - &(dict_attr_args_t){ .flags = &flags }); - if (unlikely(!new)) goto error; - - if (dict_attr_child_add(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) { - talloc_free(new); - goto error; - } - - if (dict_attr_add_to_namespace(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) { - talloc_free(new); - goto error; - } - - vendor_da = new; - } else { - fr_assert(vendor_da->type == FR_TYPE_VENDOR); - } - - if (dict_gctx_push(dctx, vendor_da) < 0) goto error; - dctx->stack[dctx->stack_depth].nest = NEST_VENDOR; - continue; - } /* BEGIN-VENDOR */ - /* * Any other string: We don't recognize it. */