/* * Copyright (c) 2004, 2005 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-union.c,v 1.11 2006/04/10 18:11:45 ca Exp $") #if SM_LIBCONF_ALONE #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-node.h" #include "sm-conf-scan.h" #include "sm-conf-state.h" #include "sm-conf-type.h" #include "sm-conf-u32.h" #include "sm-conf-util.h" /* flag */ sm_conf_type_T const sm_conf_type_union_type_data, sm_conf_type_union_choice_data; /* ** UNION_TYPE_DEFINITION -- return the sm_conf_definition_T for the union type ** ** Returns the definition for the integer in which we store ** which of the various competing substructures was selected. ** ** Parameters: ** smc -- overall context ** def -- definition of the union ** ** Returns: ** NULL if there is a programming error in the array definition ** (the array didn't have an element definition); ** otherwise, a pointer to the first candidate definition. */ static sm_conf_definition_T const * union_type_definition(sm_conf_T *smc, sm_conf_definition_T const *def) { sm_conf_definition_T const *sub; sub = sm_conf_subdef( def->scd_contents, sm_conf_type_union_type, NULL, 0); if (sub == NULL) { char loc[SM_CONF_ERROR_BUFFER_SIZE]; sm_conf_error_add(smc, "%s: %s%s" "missing definition for type of multiple-choice object", sm_conf_node_location(smc, NULL, loc, sizeof loc), def->scd_name, def->scd_name[0] == '\0'? "" : ": "); return NULL; } return sub; } /* ** UNION_NAMED_CHOICE -- (Utility) which choice does this name pick? ** ** Parameters: ** def -- the definition of the union object as a whole ** name -- '\0'-terminated name ** val_out -- assign the numeric choice value to this parameter. ** ** Returns: ** NULL if no specific subdefinition is selected, ** otherwise a pointer to the selected subdefinition. */ static sm_conf_definition_T const * union_named_choice( sm_conf_definition_T const *def, char const *name, unsigned long *val_out) { sm_conf_definition_T const *sub; if (name == NULL) return NULL; /* Look up the type in our table. */ for (sub = def->scd_contents; sub->scd_name != NULL; sub++) if ( sub->scd_type == sm_conf_type_union_choice && strcasecmp(sub->scd_name, name)) { if (val_out != NULL) *val_out = sub->scd_offset; return sub; } return NULL; } /* ** UNION_IS_EMPTY -- (Utility) is this application value empty? ** ** Parameters: ** def -- the definition of the union object as a whole ** val -- the application value ** ** Returns: ** true if the union is empty (with no alternative ** selected), false otherwise. */ static bool union_is_empty(sm_conf_definition_T const *def, void const *val) { unsigned long ul; sm_conf_definition_T const *sub; if ( val == NULL || def->scd_size == 0 || sm_conf_u32_load(val, def->scd_size, &ul) != 0 || ul != 0) return false; /* Look up the type in our table. */ for (sub = def->scd_contents; sub->scd_name != NULL; sub++) if ( sub->scd_type == sm_conf_type_union_choice && sub->scd_offset == ul) return false; return true; } /* ** UNION_VALUE_CHOICE -- (Utility) which union subtype is this? ** ** Determine which union choice defintion applies to a value, ** based on the value. ** ** Parameters: ** smc -- context ** def -- the definition of the union object as a whole ** val -- the value ** ** Returns: ** NULL if the subtype is unclear, otherwise ** the definition of the applicable subtype. */ static sm_conf_definition_T const * union_value_choice( sm_conf_T *smc, sm_conf_definition_T const *def, void const *val) { unsigned long ul; sm_conf_definition_T const *sub; if ((sub = union_type_definition(smc, def)) == NULL) return NULL; if (sm_conf_u32_load( (void *)((char *)val + sub->scd_offset), sub->scd_size, &ul) != 0) return union_named_choice(def, def->scd_default, NULL); /* Look up the type in our table. */ for (sub = def->scd_contents; sub->scd_name != NULL; sub++) if ( sub->scd_type == sm_conf_type_union_choice && sub->scd_offset == ul) return sub; return union_named_choice(def, def->scd_default, NULL); } /* ** UNION_CHOICE_MATCH -- does a given subdefinition match the node? ** ** There are multiple ways of selecting which subdefinition matches ** a node. They can be distinguished by some explicit tag that ** is present in all subtypes and has different values: ** ** vehicle { type = "car"; ... } ** ** the tag could also be a section name: ** ** vehicle "car" { ... } ** ** or they can be distinguished implicitly by testing for the ** presence of certain parameters that show up in only one of ** the subtypes: ** ** vehicle { licenseplate = "HAKRGRL"; mpg = 17; } ** ** The sm_conf_union_choice record in a choice type's details ** covers these alternatives as follows: ** ** scd_name scd_default matches ... ** ------------------------------------------------------------ ** "" NULL always (designates the default type) ** ** "" "" matches an anonymous section ** ** "" "foo" matches section "foo" (case 2 above). ** ** "bar" NULL matches anything with a ** "bar" option present (case 3 above). ** ** "bar" "foo" matches if there's a "bar" option ** with value "foo" (case 1 above). ** ** Parameters: ** smc -- overall context ** ch -- choice to match against ** section -- node we're trying to match. ** ** Returns: ** true or false -- does it match? */ static bool union_choice_match( sm_conf_T *smc, sm_conf_definition_T const *ch, sm_conf_node_T const *section) { sm_conf_node_T const *opt; size_t subdef_name_n; if (ch->scd_type != sm_conf_type_union_choice) return false; if (ch->scd_name[0] == '\0') { char const *in; size_t in_n; return ch->scd_default == NULL || ( sm_conf_section_name(smc, section, &in, &in_n) == 0 && ( in == NULL ? ch->scd_default[0] == '\0' : sm_memncaseeq(ch->scd_default, strlen(ch->scd_default), in, in_n))); } subdef_name_n = strlen(ch->scd_name); /* options with this name exist? */ opt = NULL; while ((opt = sm_conf_section_next_option(smc, section, ch->scd_name, subdef_name_n, opt)) != NULL) { char const *in; size_t in_n; /* value doesn't matter? */ if (ch->scd_default == NULL) return true; switch (sm_conf_node_type(smc, opt)) { case SM_CONF_NODE_VALUE: if ( sm_conf_value(smc, opt, &in, &in_n) == 0 && sm_memncaseeq(in, in_n, ch->scd_default, strlen(ch->scd_default))) return true; break; case SM_CONF_NODE_SECTION: if ( sm_conf_section_keyword(smc, opt, &in, &in_n) == 0 && sm_memncaseeq(in, in_n, ch->scd_default, strlen(ch->scd_default))) return true; break; default: break; } } return false; } /* ** SM_CONF_TYPE_UNION_NODE_TO_VALUE -- (Method) convert a union member ** ** Parameters: ** smc -- configuration parser state ** def -- definition of the union object ** section -- containing section ** data -- data to write to (begin of struct), or NULL ** ** Returns: ** 0 on success, a nonzero error code on error */ static int sm_conf_type_union_node_to_value( sm_conf_T *smc, sm_conf_definition_T const *def, sm_conf_node_T *section, void *data) { unsigned int flags; sm_conf_definition_T const *ch, *type; int err; char const *name; size_t name_n; if (smc == NULL) return SM_CONF_ERR_INVALID; SM_IS_CONF_DEF(def); flags = def->scd_flags; err = 0; /* Which of the subtypes applies? */ for (ch = def->scd_contents; ch->scd_name != NULL; ch++) { if (union_choice_match(smc, ch, section)) break; } /* ** If we didn't get a choice this time around, or if the choice ** is the default, make our decision based on the default node. */ if ( ( ch->scd_name == NULL || (ch->scd_default == NULL && ch->scd_name[0] == '\0')) && (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) { sm_conf_node_T const *default_node; default_node = sm_conf_section_anonymous_default(smc, name, name_n, section); if (default_node != NULL) { for (ch = def->scd_contents; ch->scd_name != NULL; ch++) { if (union_choice_match(smc, ch, default_node)) break; } } } if (ch->scd_name == NULL) { char loc[SM_CONF_ERROR_BUFFER_SIZE]; sm_conf_error_add(smc, "%s: can't determine type of multiple-choice object", sm_conf_node_location(smc, section, loc, sizeof loc)); return SM_CONF_ERR_TYPE; } err = sm_conf_type_section_node_to_value_subdef(smc, ch->scd_contents, flags | ch->scd_flags, section, data); if (err != 0) return err; /* Store chosen alternative in the type field. */ if ((type = union_type_definition(smc, def)) == NULL) return SM_CONF_ERR_TYPE; if (data == NULL) return 0; SM_LC_ISA(def, data); return sm_conf_u32_store((char *)data + type->scd_offset, type->scd_size, ch->scd_offset); } /* ** SM_CONF_TYPE_UNION_VALUE_CHECK -- (Method) check validity of ** union application data ** ** Parameters: ** smc -- configuration parser state ** def -- definition of the union object ** data -- data to write to, or NULL ** ** Returns: ** 0 on success, a nonzero error code on error */ static int sm_conf_type_union_value_check( sm_conf_T *smc, sm_conf_definition_T const *def, void const *data) { sm_conf_definition_T const *ch, *d; int err; SM_IS_CONF_DEF(def); SM_REQUIRE(smc != NULL); if (data == NULL) return 0; ch = union_value_choice(smc, def, data); if (ch == NULL) { char loc[SM_CONF_ERROR_BUFFER_SIZE]; /* ** Allow a value of 0 for the type choice if a ** multiple choice object is simply absent. */ if ( (def->scd_flags & SM_CONF_FLAG_REQUIRED) || !union_is_empty(def, data)) { sm_conf_error_add(smc, "%s: unclear type of multiple-choice object", sm_conf_node_location(smc, NULL, loc, sizeof loc)); return SM_CONF_ERR_TYPE; } return 0; } /* recursively check the chosen subtype's contents. */ for (d = ch->scd_contents; d != NULL && d->scd_name != NULL; d++) { err = (* d->scd_type->sctp_value_check)(smc, d, (char *)data + d->scd_offset); if (err != 0) return err; } /* if the subtype has a check function, use it. */ if (ch->scd_check != NULL) { err = (* ch->scd_check)(smc, ch->scd_check_data, ch, data); if (err != 0) return err; } /* if we have our own 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_UNION_VALUE_NULL -- (Method) zero out union application data ** ** Parameters: ** smc -- configuration parser state ** def -- definition of the union object ** data -- data to write to (field of struct), or NULL ** ** Returns: ** 0 on success, a nonzero error code on error */ static int sm_conf_type_union_value_null( sm_conf_T *smc, sm_conf_definition_T const *def, void *data) { sm_conf_definition_T const *ch, *d; int err; SM_REQUIRE(smc != NULL); SM_IS_CONF_DEF(def); if (data == NULL) return 0; if (def->scd_size > 0) sm_memset(data, 0, def->scd_size); if (def->scd_default != NULL) { unsigned long ch_val; ch = union_named_choice(def, def->scd_default, &ch_val); if (ch != NULL) { if (def->scd_size != 0) { err = sm_conf_u32_store( data, def->scd_size, ch_val); if (err != 0) return err; } for (d = ch->scd_contents; d->scd_name != NULL; d++) { err = (* d->scd_type->sctp_value_null)( smc, d, (char *)data + d->scd_offset); if (err != 0) return err; } } else { char loc[SM_CONF_ERROR_BUFFER_SIZE]; sm_conf_error_add(smc, "%s:%s%s default selection \"%s\" doesn't " "exist in union definition", sm_conf_node_location(smc, NULL, loc, sizeof loc), def->scd_name, def->scd_name[0] == '\0'? "" : ": ", def->scd_default); return SM_CONF_ERR_TYPE; } } return 0; } sm_conf_type_T const sm_conf_type_union_data = { sm_conf_type_union_node_to_value, sm_conf_type_union_value_check, sm_conf_type_union_value_null };