/* * Copyright (c) 2004-2006 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: sm-conf-type-section.c,v 1.23 2006/04/13 16:38:50 ca Exp $") #if SM_LIBCONF_ALONE #include #include #include #include "sm-conf.h" #else /* SM_LIBCONF_ALONE */ #include "sm/string.h" #include "sm/ctype.h" #include "sm/memops.h" #include "sm/sm-conf.h" #endif /* SM_LIBCONF_ALONE */ #include "sm-conf-util.h" #include "sm-conf-node.h" #include "sm-conf-scan.h" #include "sm-conf-state.h" #include "sm-conf-type.h" /* ** DEFINITION_FOR_SECTION -- (Utility) return the best definition ** for [ {} ] ** ** We're looking at a section with a keyword and a title. ** There may be different definitions for sections ** with identical keywords but different titles. ** ** (That is, "server [smtp]" may be a completely different ** data type from "server [imap]".) ** ** An exact, more specific match overrides a generic, ** keyword-only match. ** ** Parameters: ** smc -- configuration parser state ** def -- definition for this section ** kw -- identifier we're lookign for ** kw_n -- # of bytes pointed to by , need ** not be '\0'-terminated ** name -- title after the identifier, or NULL ** name_n -- # of bytes pointed to by , need ** not be '\0'-terminated. ** ** Returns: ** NULL if no matching definition is found, ** otherwise a pointer to the applicable definition. */ static sm_conf_definition_T const * definition_for_section( sm_conf_T *smc, sm_conf_definition_T const *def, char const *kw, size_t kw_n, char const *name, size_t name_n) { char const *d, *p; sm_conf_definition_T const *generic; char const *kw_e; char cl; static char const obraces[] = "<({[\"'`", cbraces[] = ">)}]\"''"; if (def == NULL) return NULL; SM_IS_CONF_DEF(def); SM_REQUIRE(smc != NULL); generic = NULL; kw_e = (kw == NULL ? NULL : kw + kw_n); while (def->scd_name != NULL) { p = kw; d = def->scd_name; while (isascii(*d) && (isalnum(*d) || *d == '_')) { if ( p >= kw_e || !isascii(*p) || tolower(*d) != tolower(*p)) goto next; d++; p++; } if (p != kw_e) goto next; if (name == NULL) { if (*d != '\0') goto next; return def; } if (*d == '\0') { generic = def; goto next; } cl = 0; if ((p = strchr(obraces, *d)) != NULL) { d++; cl = cbraces[p - obraces]; } p = d; /* ignore closing bracket */ d += strlen(d); if (d[-1] == cl) d--; if (sm_conf_token_match(smc, p, d - p, name, name_n)) return def; next: def++; } return generic; } /* ** SECTION_ELEMENT_ERROR -- (Utility) print an error ** ** If name is non-NULL, the error is preceded not just ** by the ndoe location but also by the involved element name. ** ** Parameters: ** handle -- the configuration parser state ** node -- NULL or node that the error occured with ** name -- name of the section element, or NULL ** name_n -- if name is non-NULL, # of bytes pointed to by ** (doesn't have to be '\0'-terminated) ** errstring -- error message to log ** ** Returns: ** None ** ** Side effect: ** Logs an error to the per-configuration-parser error ** state. */ static void section_element_error( sm_conf_T *handle, sm_conf_node_T const *node, char const *name, size_t name_n, char const *errstring) { char loc[SM_CONF_ERROR_BUFFER_SIZE]; SM_REQUIRE(handle != NULL); if (name_n > 0 && name != NULL) sm_conf_error_add(handle, "%s: %*s: %s", sm_conf_node_location(handle, node, loc, sizeof loc), (int)name_n, name, errstring); else sm_conf_error_add(handle, "%s: %s", sm_conf_node_location(handle, node, loc, sizeof loc), errstring); } /* ** SM_CONF_SECTION_ANONYMOUS_DEFAULT -- (Utility) find a node to ** default from (1) ** ** Look in this node's ancestral chain for sections of the ** specified type (keyword) with no name directly contained in ** an ancestor. ** ** Return the lowest section that is or above it. ** ** Parameters: ** handle -- the context ** kw -- type (keyword) we're looking for ** kw_n -- length kw. ** node -- where to start looking ** ** Returns: ** The lowest section that is or above it. */ sm_conf_node_T * sm_conf_section_anonymous_default( sm_conf_T *handle, char const *kw, size_t kw_n, sm_conf_node_T const *node) { char const *snm; size_t snm_n; sm_conf_node_T *sib; if (node == NULL) return NULL; while ( (node = sm_conf_node_parent(handle, node)) != NULL && sm_conf_node_type(handle, node) == SM_CONF_NODE_SECTION) { sib = NULL; while ( (sib = sm_conf_section_next_subsection(handle, node, kw, kw_n, "", 0, sib)) != NULL) { if (sm_conf_section_name(handle, sib, &snm, &snm_n) == 0 && snm == NULL) return sib; } } return NULL; } /* ** SM_CONF_SECTION_ENVIRONMENT_DEFAULT -- (Utility) find a node ** to default from (2) ** ** Look in this node's ancestral chain for a containing ** section that contains the specified property. ** Return the lowest section that has it. ** ** Parameters: ** handle -- the context ** name -- property we're looking for ** name_n -- length of . ** node -- where to start looking ** ** Returns: ** Lowest container with a suitable default value, ** or NULL if none was found. */ static sm_conf_node_T const * sm_conf_section_environment_default( sm_conf_T *handle, char const *name, size_t name_n, sm_conf_node_T const *node) { while ( (node = sm_conf_node_parent(handle, node)) != NULL && sm_conf_node_type(handle, node) == SM_CONF_NODE_SECTION) { if (sm_conf_section_next_option(handle, node, name, name_n, NULL) != NULL) return node; } return NULL; } /* ** SM_CONF_SCAN_SECTION_DEFAULT_ELEMENT_NODE -- (Utility) scan default values ** ** We're trying to default one element of a section. ** (We've got its definition.) We've found another section ** that could serve as a template for the one we're parsing ** right now. ** ** Scout the template section for values to assing to the ** empty element. If you find one, use it. (If the element ** is multivalued, keep on scanning after finding one; there ** may be others.) ** ** Parameters: ** handle -- the configuration parser context ** def -- section definition for the element we're ** trying to assign. ** node -- the container section that we're pulling ** defaults out of ** data -- user data (begin of struct) ** ** Returns: ** If no elements could be defaulted form the surrounding ** section, the call returns SM_CONF_ERR_NOT_FOUND. ** Otherwise, it returns 0 on success, ** potentially other nonzero error codes on error. */ static int sm_conf_scan_section_default_element_node( sm_conf_T *handle, sm_conf_definition_T const *def, sm_conf_node_T const *node, void *data) { size_t def_name_n; int any = 0; int err = 0; sm_conf_node_T *elem; sm_conf_node_T *pred; SM_IS_CONF_DEF(def); def_name_n = strlen(def->scd_name); SM_LC_ISA(def, data); pred = NULL; for (;;) { elem = sm_conf_section_next_option(handle, node, def->scd_name, def_name_n, pred); if (elem == NULL) break; pred = elem; any = 1; err = sm_conf_scan_node_to_value(handle, def->scd_name, strlen(def->scd_name), def, elem, data == NULL ? NULL : (char *)data + def->scd_offset); if (err != 0) return err; if (!(def->scd_flags & SM_CONF_FLAG_MULTIPLE)) break; } if (!any) return SM_CONF_ERR_NOT_FOUND; if (data == NULL) return 0; #if SM_LIBCONF_ISSET if (def->scd_isset_offset > 0) *((uchar *)data + def->scd_isset_offset) = SM_LC_SET_FRM_ENV; #endif return sm_conf_scan_value_check(handle, def->scd_name, strlen(def->scd_name), def, node, data); } /* ** SM_CONF_SCAN_SECTION_DEFAULT_ELEMENT -- (Utility) default an element. ** ** Find sections we might be defaulting from in the environment ** and feed them to sm_conf_scan_section_default_element_node() ** for closer examination, until we find something or run out. ** ** Parameters: ** handle -- the configuration parser context ** def -- section definition for the element we're ** trying to assign. ** section -- the section we're defaulting in. ** default_node -- corresponding anonymous default node ** in the same parent, if any. ** data -- user data (begin of struct), or NULL ** ** Returns: ** If no elements could be defaulted form the surrounding ** section, the call returns SM_CONF_ERR_NOT_FOUND. ** Otherwise, it returns 0 on success, ** potentially other nonzero error codes on error. */ static int sm_conf_scan_section_default_element( sm_conf_T *handle, sm_conf_definition_T const *def, sm_conf_node_T const *section, sm_conf_node_T const *default_node, void *data) { size_t def_name_n; int err; SM_IS_CONF_DEF(def); err = 0; def_name_n = strlen(def->scd_name); /* ** Try defaulting the element using our corresponding ** anonymous default_node, if any. */ if (default_node != NULL) { err = sm_conf_scan_section_default_element_node(handle, def, default_node, data); if (err != SM_CONF_ERR_NOT_FOUND) return err; } /* ** If that didn't find anything, and we've got a named property, ** and this particular property drifts down from the environment, ** look for a surrounding section that contains it, and use ** those section's settings. */ if ( !(def->scd_flags & SM_CONF_FLAG_DEFAULT_FROM_ENVIRONMENT) || (default_node = sm_conf_section_environment_default(handle, def->scd_name, strlen(def->scd_name), section)) == NULL) return SM_CONF_ERR_NOT_FOUND; return sm_conf_scan_section_default_element_node(handle, def, default_node, data); } /* ** SM_CONF_TYPE_SECTION_TITLE_NODE_TO_VALUE -- (Method) store a title ** ** If specified in a section's definitions, convert the name ** of a section to a data string and store it. ** ** This is how you access the "bar" in ** ** foo "bar" ** { ** ... ** } ** ** Parameters: ** smc -- the configuration parser context ** def -- definition for the section title ** section -- the section whose title is being scanned ** data -- user data (element itself), or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ static int sm_conf_type_section_title_node_to_value( sm_conf_T *smc, sm_conf_definition_T const *def, sm_conf_node_T *section, void *data) { int err; char const *name; size_t name_n; char loc[SM_CONF_ERROR_BUFFER_SIZE]; SM_IS_CONF_DEF(def); err = sm_conf_section_name(smc, section, &name, &name_n); if (err != 0) return err; if (name_n > 0 && memchr(name, '\0', name_n) != NULL) { sm_conf_error_add(smc, "%s: NUL in section title '%.*s'", sm_conf_node_location(smc, section, loc, sizeof loc), (int)name_n, name); return SM_CONF_ERR_TYPE; } if (data == NULL) return 0; SM_LC_ISA(def, data); if (def->scd_size == 0) *(char const **)data = name; else { if (name_n >= def->scd_size) { sm_conf_error_add(smc, "%s: overflow error: section " "title '%.*s' more than %lu character%s long", sm_conf_node_location(smc, section, loc, sizeof loc), (int)name_n, name, (unsigned long)def->scd_size - 1, def->scd_size == 2 ? "" : "s"); return SM_CONF_ERR_TYPE; } if (name_n != 0) memcpy(data, name, name_n); ((char *)data)[name_n] = '\0'; } return 0; } /* ** SM_CONF_TYPE_SECTION_TITLE_VALUE_CHECK -- (Method) check a value ** ** (Not yet implemented/No-OP) ** ** Parameters: ** smc -- the configuration parser context ** def -- definition for the section title ** data -- user data, or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ static int sm_conf_type_section_title_value_check( sm_conf_T *smc, sm_conf_definition_T const *def, void const *data) { SM_IS_CONF_DEF(def); return 0; } /* ** SM_CONF_TYPE_SECTION_TITLE_VALUE_NULL -- (Method) zero out a value ** ** Parameters: ** smc -- the configuration parser context ** def -- definition for the section title ** data -- user data (element itself), or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ static int sm_conf_type_section_title_value_null( sm_conf_T *smc, sm_conf_definition_T const *def, void *data) { SM_IS_CONF_DEF(def); if (def->scd_size > 0) sm_memset(data, 0, def->scd_size); else *(char const **)data = NULL; return 0; } sm_conf_type_T const sm_conf_type_section_title_data = { sm_conf_type_section_title_node_to_value, sm_conf_type_section_title_value_check, sm_conf_type_section_title_value_null }; /* ** SM_CONF_TYPE_SECTION_NODE_TO_VALUE_TITLE -- (Utiltiy) convert section title ** ** This utility not only triggers conversion and storage ** of the title itself, it also checks for title-related ** constraint violation (titles of sections that mustn't ** have one, or the opposite.) ** ** Parameters: ** smc -- the configuration parser context ** defs -- array of definition for the section members ** flags -- section flags ** section -- section node ** data -- user data (begin of struct), or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ static int sm_conf_type_section_node_to_value_title( sm_conf_T *smc, sm_conf_definition_T const *defs, unsigned int flags, sm_conf_node_T *section, void *data) { int err; char const *name; size_t name_n; sm_conf_definition_T const *sub; /* Scan the section name, if there is one. */ err = sm_conf_section_name(smc, section, &name, &name_n); if (err != 0) return err; /* check for constraints on the whole section. */ if ( (flags & SM_CONF_FLAG_SECTION_MUST_BE_NAMED) && name == NULL) { if (sm_conf_section_keyword(smc, section, &name, &name_n)) { name = NULL; name_n = 0; } section_element_error(smc, section, name, name_n, "section must have a name"); return SM_CONF_ERR_TYPE; } else if ( (flags & SM_CONF_FLAG_SECTION_MUST_BE_ANONYMOUS) && name != NULL) { section_element_error(smc, section, name, name_n, "section cannot have a name"); return SM_CONF_ERR_TYPE; } sub = sm_conf_subdef(defs, sm_conf_type_section_title, NULL, 0); if (sub == NULL) return 0; return sm_conf_type_section_title_node_to_value(smc, sub, section, data == NULL ? NULL : (char *)data + sub->scd_offset); } /* ** SM_CONF_TYPE_SECTION_NODE_TO_VALUE_SUBDEF -- ** (Utiltiy) convert section members ** ** Parameters: ** smc -- the configuration parser context ** defs -- array of definition for the section members ** flags -- section flags ** section -- whole section node ** data -- user data (begin of struct), or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ int sm_conf_type_section_node_to_value_subdef( sm_conf_T *smc, sm_conf_definition_T const *defs, unsigned int flags, sm_conf_node_T *section, void *data) { sm_conf_definition_T const *def; sm_conf_node_T *default_node; sm_conf_node_T *sub; char const *name; size_t name_n; int err, section_error; char seen_buf[256], *seen_ptr; size_t seen_m; if (smc == NULL) return SM_CONF_ERR_INVALID; err = 0; memset(seen_buf, 0, sizeof seen_buf); seen_ptr = seen_buf; seen_m = sizeof seen_buf; if ( (flags & SM_CONF_FLAG_SECTION_MUST_BE_NAMED) && (flags & SM_CONF_FLAG_SECTION_MUST_BE_ANONYMOUS)) { section_element_error(smc, section, NULL, 0, "definition " "requires that section be both named and anonymous?"); return SM_CONF_ERR_INVALID; } /* ** If we default from anonymous sections, and this section ** isn't anonymous, find one we can default from. */ if ( (flags & SM_CONF_FLAG_SECTION_DEFAULT_FROM_ANONYMOUS) && section != NULL && sm_conf_section_name(smc, section, &name, &name_n) == 0 && name != NULL && sm_conf_section_keyword(smc, section, &name, &name_n) == 0) default_node = sm_conf_section_anonymous_default(smc, name, name_n, section); else default_node = NULL; /* Assign NULL values to anything that doesn't keep its default */ if (defs != NULL && data != NULL) { for (def = defs; def != NULL && def->scd_name != NULL; def++) { if (!( (flags | def->scd_flags) & ( SM_CONF_FLAG_KEEP_DEFAULT | SM_CONF_FLAG_PARSE_ONLY))) { SM_LC_ISA(def, data); err = (* def->scd_type->sctp_value_null)( smc, def, (char *)data + def->scd_offset); if (err != 0) return err; } } } section_error = 0; if (section != NULL) { err = sm_conf_type_section_node_to_value_title( smc, defs, flags, section, data); if (err != 0) { section_error = err; err = 0; } /* Scan all section elements. */ sub = NULL; while ((sub = sm_conf_section_next(smc, section, &name, &name_n, sub)) != NULL) { char const *sub_kw, *sub_name; size_t sub_kw_n, sub_name_n; if (name_n == 0) name = NULL; def = NULL; if (name != NULL) def = sm_conf_subdef(defs, NULL, name, name_n); else { if ( sm_conf_section_keyword(smc, sub, &sub_kw, &sub_kw_n) == 0 && sm_conf_section_name(smc, sub, &sub_name, &sub_name_n) == 0) { def = definition_for_section(smc, defs, sub_kw, sub_kw_n, sub_name, sub_name_n); } } /* def is now NULL or the applicable definition. */ if (def == NULL) { /* Undefined name. Do we allow that? */ if (!(flags & ( name == NULL ? SM_CONF_FLAG_ALLOW_ANY_SECTION : SM_CONF_FLAG_ALLOW_ANY_OPTION))) { if (name == NULL) section_element_error(smc, sub, sub_kw, sub_kw_n, "unexpected section"); else section_element_error(smc, sub, name, name_n, "unexpected option"); if (section_error == 0) section_error = SM_CONF_ERR_TYPE; } /* ** We have nowhere to store this value now, ** but other parts of the using application ** may ask for it later by name, e.g. ** based on user input. */ continue; } if (!(def->scd_flags & SM_CONF_FLAG_MULTIPLE)) { size_t bit_i; unsigned char bit_mask; bit_i = (def - defs) / CHAR_BIT; bit_mask = 1 << ((def - defs) % CHAR_BIT); if (bit_i >= seen_m) { sm_conf_definition_T const *hi; SM_ASSERT(seen_ptr == seen_buf); for (hi = def; hi->scd_name != NULL; hi++) ; seen_m = ((hi - defs) + (CHAR_BIT - 1)) / CHAR_BIT; seen_ptr = malloc(seen_m); if (seen_ptr == NULL) return SM_CONF_ERR_NO_MEMORY; memset(seen_ptr, 0, seen_m); memcpy(seen_ptr, seen_buf, sizeof seen_buf); } if (seen_ptr[bit_i] & bit_mask) { section_element_error(smc, section, def->scd_name, strlen(def->scd_name), name == NULL ? "duplicate section" : "duplicate option"); section_error = SM_CONF_ERR_ALREADY; } seen_ptr[bit_i] |= bit_mask; } if ((def->scd_flags | flags) & SM_CONF_FLAG_PARSE_ONLY) { err = sm_conf_scan_node_to_value(smc, name, name_n, def, sub, NULL); } else { SM_LC_ISA(def, data); /* ** Inherit the default value for a multiple ** choice value now so it can be changed ** "locally". */ if (sm_conf_type_choice == def->scd_type && data != NULL && (def->scd_flags & SM_CONF_FLAG_OR) != 0 && (def->scd_flags & SM_CONF_FLAG_STRICTLY_REQUIRED) == 0) { err = sm_conf_scan_section_default_element( smc, def, section, default_node, data); } err = sm_conf_scan_node_to_value(smc, name, name_n, def, sub, data == NULL ? NULL : (char *)data + def->scd_offset); #if SM_LIBCONF_ISSET /* ** Use different values depending on how data ** got set?? */ if (0 == err && data != NULL && def->scd_isset_offset > 0) { *((uchar *)data + def->scd_isset_offset) = SM_LC_SET_DIRECT; } #endif } if (err != 0) section_error = err; /* If there was an error, find more. */ } } if (section_error != 0) { if (seen_ptr != seen_buf) free(seen_ptr); return section_error; } /* ** Confirm that all mandatory elements of the definition did occur. ** If we can default elements, default those that didn't occur. */ for (def = defs; def != NULL && def->scd_name != NULL; def++) { sm_conf_node_T const *elem; elem = NULL; if ( section != NULL && def->scd_type == sm_conf_type_section_title) { if ( (def->scd_flags & ( SM_CONF_FLAG_STRICTLY_REQUIRED | SM_CONF_FLAG_REQUIRED)) && sm_conf_section_name(smc, section, &name, &name_n) == 0 && name == NULL) { if (sm_conf_section_keyword(smc, section, &name, &name_n)) { name = NULL; name_n = 0; } section_element_error(smc, section, name, name_n, "section must have a name"); return SM_CONF_ERR_TYPE; } err = sm_conf_scan_value_check(smc, "section title", sizeof("section title") - 1, def, section, data == NULL ? NULL : (char *)data + def->scd_offset); if (err != 0) section_error = err; /* The section name is never defaulted. */ continue; } if (section != NULL) elem = sm_conf_section_next_option( smc, section, def->scd_name, strlen(def->scd_name), NULL); if (elem == NULL) { /* ** The real node didn't match this definition. ** See if we default from something. */ err = SM_CONF_ERR_NOT_FOUND; if (!(def->scd_flags & SM_CONF_FLAG_STRICTLY_REQUIRED)) { err = sm_conf_scan_section_default_element( smc, def, section, default_node, data); if (err != 0) { if ( err == SM_CONF_ERR_NOT_FOUND && !( def->scd_flags & SM_CONF_FLAG_REQUIRED)) /* it was optional. */ err = 0; } } if (err != 0) { section_element_error(smc, section, def->scd_name, strlen(def->scd_name), def->scd_name[0] == '\0' ? "missing value" : "missing property"); section_error = err; } } /* ** Now that it's been fully converted, check the constraint ** for this element. */ if (section_error == 0 && data != NULL) { err = sm_conf_scan_value_check(smc, def->scd_name, strlen(def->scd_name), def, elem, (char *)data + def->scd_offset); if (err != 0) section_error = err; } } if (seen_ptr != seen_buf) free(seen_ptr); return section_error; } /* ** SM_CONF_TYPE_SECTION_NODE_TO_VALUE -- (Method) convert section ** ** Parameters: ** smc -- the configuration parser context ** section_def -- definition for the section ** section -- whole section node ** data -- user data (begin of struct), or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ static int sm_conf_type_section_node_to_value( sm_conf_T *smc, sm_conf_definition_T const *section_def, sm_conf_node_T *section, void *data) { return sm_conf_type_section_node_to_value_subdef( smc, section_def->scd_contents, section_def->scd_flags, section, data); } /* ** SM_CONF_TYPE_SECTION_VALUE_CHECK -- (Method) check value against ** section constraints ** ** Parameters: ** smc -- the configuration parser context ** def -- definition for the section ** data -- user data (begin of struct), or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ static int sm_conf_type_section_value_check( sm_conf_T *smc, sm_conf_definition_T const *def, void const *data) { sm_conf_definition_T const *sub; int err; SM_IS_CONF_DEF(def); /* If we have nothing to check, don't do anything. */ if (data == NULL) return 0; /* recursively check the section's contents. */ for (sub = def->scd_contents; sub != NULL && sub->scd_name != NULL; sub++) { err = (* sub->scd_type->sctp_value_check)(smc, sub, (char *)data + sub->scd_offset); if (err != 0) return err; } /* if we have a check function, use it. */ if (def->scd_check != NULL) return (* def->scd_check)(smc, def->scd_check_data, def, data); return 0; } /* ** SM_CONF_TYPE_SECTION_VALUE_NULL -- (Method) zero out a data buffer ** ** Parameters: ** smc -- the configuration parser context ** def -- definition for the section ** data -- user data, or NULL ** ** Returns: ** 0 on success, a nonzero error on error. */ static int sm_conf_type_section_value_null( sm_conf_T *smc, sm_conf_definition_T const *def, void *data) { sm_conf_definition_T const *d; int err; SM_IS_CONF_DEF(def); if (data == NULL) return 0; /* ** This zero's out the user struct; is this really necessary?? ** It destroy any magic that's in there! It is only prevented if ** SM_CONF_FLAG_KEEP_DEFAULT (or SM_CONF_FLAG_PARSE_ONLY) is set! */ if (def->scd_size > 0) sm_memset(data, 0, def->scd_size); for (d = def->scd_contents; d != NULL && d->scd_name != NULL; d++) { SM_LC_ISA(d, data); err = (* d->scd_type->sctp_value_null)( smc, d, (char *)data + d->scd_offset); if (err != 0) return err; } return 0; } sm_conf_type_T const sm_conf_type_section_data = { sm_conf_type_section_node_to_value, sm_conf_type_section_value_check, sm_conf_type_section_value_null };