diff --git a/CHANGES b/CHANGES index 3c6748d98c..423ffb640e 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,19 @@ === ============================================================================== +------------------------------------------------------------------------------ +--- Functionality changes from Asterisk 12 to Asterisk 13 -------------------- +------------------------------------------------------------------------------ + +ConfBridge +-------------------------- + * CONFBRIDGE dialplan function is now capable of creating/modifying dynamic + conference user menus. + + * CONFBRIDGE dialplan function is now capable of removing dynamic conference + menus, bridge settings, and user settings that have been applied by the + CONFBRIDGE dialplan function. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 11 to Asterisk 12 -------------------- ------------------------------------------------------------------------------ diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index 6f1420fcd2..d16011b1da 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -100,8 +100,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") The name of the DTMF menu in confbridge.conf to be applied to - this channel. No menu is applied by default if this option is left - blank. + this channel. When left blank, a dynamically built menu profile + created by the CONFBRIDGE dialplan function is searched for on + the channel and used. If no dynamic profile is present, the + 'default_menu' profile found in confbridge.conf is used. @@ -116,14 +118,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - Set a custom dynamic bridge and user profile on a channel for the ConfBridge application using the same options defined in confbridge.conf. + Set a custom dynamic bridge, user, or menu profile on a channel for the ConfBridge application using the same options defined in confbridge.conf. - Type refers to which type of profile the option belongs too. Type can be bridge or user. + Type refers to which type of profile the option belongs too. Type can be bridge, user, or + menu. - Option refers to confbridge.conf option that is being set dynamically on this channel. + Option refers to confbridge.conf option that is being set dynamically on this channel, or + clear to remove already applied options from the channel. @@ -1504,8 +1508,9 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) int res = 0, volume_adjustments[2]; int quiet = 0; char *parse; - const char *b_profile_name = DEFAULT_BRIDGE_PROFILE; - const char *u_profile_name = DEFAULT_USER_PROFILE; + const char *b_profile_name = NULL; + const char *u_profile_name = NULL; + const char *menu_profile_name = NULL; struct confbridge_conference *conference = NULL; struct confbridge_user user = { .chan = chan, @@ -1517,7 +1522,7 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) AST_APP_ARG(conf_name); AST_APP_ARG(b_profile_name); AST_APP_ARG(u_profile_name); - AST_APP_ARG(menu_name); + AST_APP_ARG(menu_profile_name); ); if (ast_channel_state(chan) != AST_STATE_UP) { @@ -1545,7 +1550,8 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) b_profile_name = args.b_profile_name; } if (!conf_find_bridge_profile(chan, b_profile_name, &user.b_profile)) { - ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name); + ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name ? + b_profile_name : DEFAULT_BRIDGE_PROFILE); res = -1; goto confbridge_cleanup; } @@ -1555,7 +1561,8 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) u_profile_name = args.u_profile_name; } if (!conf_find_user_profile(chan, u_profile_name, &user.u_profile)) { - ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name); + ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name ? + u_profile_name : DEFAULT_USER_PROFILE); res = -1; goto confbridge_cleanup; } @@ -1577,14 +1584,15 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) } /* menu name */ - if (args.argc > 3 && !ast_strlen_zero(args.menu_name)) { - ast_copy_string(user.menu_name, args.menu_name, sizeof(user.menu_name)); - if (conf_set_menu_to_user(user.menu_name, &user)) { - ast_log(LOG_WARNING, "Conference menu %s does not exist and can not be applied to confbridge user.\n", - args.menu_name); - res = -1; /* invalid PIN */ - goto confbridge_cleanup; - } + if (args.argc > 3 && !ast_strlen_zero(args.menu_profile_name)) { + menu_profile_name = args.menu_profile_name; + } + + if (conf_set_menu_to_user(chan, &user, menu_profile_name)) { + ast_log(LOG_WARNING, "Conference menu profile %s does not exist\n", menu_profile_name ? + menu_profile_name : DEFAULT_MENU_PROFILE); + res = -1; + goto confbridge_cleanup; } /* Set if DTMF should pass through for this user or not */ diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c index 97c0696a7d..5c430ea3a1 100644 --- a/apps/confbridge/conf_config_parser.c +++ b/apps/confbridge/conf_config_parser.c @@ -419,6 +419,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + + When using the CONFBRIDGE dialplan function, use a menu profile as a template for creating a new temporary profile + DTMF sequences to assign various confbridge actions to @@ -896,15 +899,25 @@ static int set_sound(const char *sound_name, const char *sound_file, struct brid struct func_confbridge_data { struct bridge_profile b_profile; struct user_profile u_profile; + struct conf_menu *menu; unsigned int b_usable:1; /*!< Tells if bridge profile is usable or not */ unsigned int u_usable:1; /*!< Tells if user profile is usable or not */ + unsigned int m_usable:1; /*!< Tells if menu profile is usable or not */ }; + +static void func_confbridge_data_destructor(struct func_confbridge_data *b_data) +{ + conf_bridge_profile_destroy(&b_data->b_profile); + ao2_cleanup(b_data->menu); + ast_free(b_data); +} + static void func_confbridge_destroy_cb(void *data) { struct func_confbridge_data *b_data = data; - conf_bridge_profile_destroy(&b_data->b_profile); - ast_free(b_data); + func_confbridge_data_destructor(b_data); }; + static const struct ast_datastore_info confbridge_datastore = { .type = "confbridge", .destroy = func_confbridge_destroy_cb @@ -944,14 +957,18 @@ int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data ast_datastore_free(datastore); return 0; } + datastore->data = b_data; b_data->b_profile.sounds = bridge_profile_sounds_alloc(); if (!b_data->b_profile.sounds) { ast_channel_unlock(chan); ast_datastore_free(datastore); - ast_free(b_data); return 0; } - datastore->data = b_data; + if (!(b_data->menu = menu_alloc("dialplan"))) { + ast_channel_unlock(chan); + ast_datastore_free(datastore); + return 0; + } ast_channel_datastore_add(chan, datastore); } else { b_data = datastore->data; @@ -966,15 +983,48 @@ int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data tmpvar.value = value; tmpvar.file = "CONFBRIDGE"; if (!strcasecmp(args.type, "bridge")) { - if (!aco_process_var(&bridge_type, "dialplan", &tmpvar, &b_data->b_profile)) { + if (!strcasecmp(args.option, "clear")) { + b_data->b_usable = 0; + conf_bridge_profile_destroy(&b_data->b_profile); + memset(&b_data->b_profile, 0, sizeof(b_data->b_profile)) ; + if (!(b_data->b_profile.sounds = bridge_profile_sounds_alloc())) { + /* If this reallocation fails, the datastore has become unusable and must be destroyed. */ + ast_channel_lock(chan); + ast_channel_datastore_remove(chan, datastore); + ast_channel_unlock(chan); + ast_datastore_free(datastore); + } + return 0; + } else if (!aco_process_var(&bridge_type, "dialplan", &tmpvar, &b_data->b_profile)) { b_data->b_usable = 1; return 0; } } else if (!strcasecmp(args.type, "user")) { - if (!aco_process_var(&user_type, "dialplan", &tmpvar, &b_data->u_profile)) { + if (!strcasecmp(args.option, "clear")) { + b_data->u_usable = 0; + user_profile_destructor(&b_data->u_profile); + memset(&b_data->u_profile, 0, sizeof(b_data->u_profile)); + return 0; + } else if (!aco_process_var(&user_type, "dialplan", &tmpvar, &b_data->u_profile)) { b_data->u_usable = 1; return 0; } + } else if (!strcasecmp(args.type, "menu")) { + if (!strcasecmp(args.option, "clear")) { + b_data->m_usable = 0; + ao2_cleanup(b_data->menu); + if (!(b_data->menu = menu_alloc("dialplan"))) { + /* If this reallocation fails, the datastore has become unusable and must be destroyed */ + ast_channel_lock(chan); + ast_channel_datastore_remove(chan, datastore); + ast_channel_unlock(chan); + ast_datastore_free(datastore); + } + return 0; + } else if (!aco_process_var(&menu_type, "dialplan", &tmpvar, b_data->menu)) { + b_data->m_usable = 1; + return 0; + } } ast_log(LOG_WARNING, "%s(%s,%s) cannot be set to '%s'. Invalid type, option, or value.\n", @@ -1852,6 +1902,70 @@ static int bridge_template_handler(const struct aco_option *opt, struct ast_vari return 0; } +static int copy_menu_entry(struct conf_menu_entry *dst, struct conf_menu_entry *src) +{ + struct conf_menu_action *menu_action; + struct conf_menu_action *new_menu_action; + + ast_copy_string(dst->dtmf, src->dtmf, sizeof(dst->dtmf)); + AST_LIST_HEAD_INIT_NOLOCK(&dst->actions); + + AST_LIST_TRAVERSE(&src->actions, menu_action, action) { + if (!(new_menu_action = ast_calloc(1, sizeof(*new_menu_action)))) { + return -1; + } + memcpy(new_menu_action, menu_action, sizeof(*new_menu_action)); + AST_LIST_NEXT(new_menu_action, action) = NULL; + AST_LIST_INSERT_TAIL(&dst->actions, new_menu_action, action); + } + + return 0; +} + +static int conf_menu_profile_copy(struct conf_menu *dst, struct conf_menu *src) +{ + /* Copy each menu item to the dst struct */ + struct conf_menu_entry *cur; + + AST_LIST_TRAVERSE(&src->entries, cur, entry) { + struct conf_menu_entry *cpy; + + if (!(cpy = ast_calloc(1, sizeof(*cpy)))) { + return -1; + } + + if (copy_menu_entry(cpy, cur)) { + conf_menu_entry_destroy(cpy); + ast_free(cpy); + return -1; + } + AST_LIST_INSERT_TAIL(&dst->entries, cpy, entry); + } + + return 0; +} + +static int menu_template_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct conf_menu *dst_menu = obj; + struct confbridge_cfg *cfg = aco_pending_config(&cfg_info); + RAII_VAR(struct conf_menu *, src_menu, NULL, ao2_cleanup); + + if (!cfg) { + return 0; + } + + if (!(src_menu = ao2_find(cfg->menus, var->value, OBJ_KEY))) { + return -1; + } + + if (conf_menu_profile_copy(dst_menu, src_menu)) { + return -1; + } + + return 0; +} + static int sound_option_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { set_sound(var->name, var->value, obj); @@ -1868,6 +1982,7 @@ static int verify_default_profiles(void) { RAII_VAR(struct user_profile *, user_profile, NULL, ao2_cleanup); RAII_VAR(struct bridge_profile *, bridge_profile, NULL, ao2_cleanup); + RAII_VAR(struct conf_menu *, menu_profile, NULL, ao2_cleanup); struct confbridge_cfg *cfg = aco_pending_config(&cfg_info); if (!cfg) { @@ -1896,6 +2011,17 @@ static int verify_default_profiles(void) ao2_link(cfg->user_profiles, user_profile); } + menu_profile = ao2_find(cfg->menus, DEFAULT_MENU_PROFILE, OBJ_KEY); + if (!menu_profile) { + menu_profile = menu_alloc(DEFAULT_MENU_PROFILE); + if (!menu_profile) { + return -1; + } + ast_log(AST_LOG_NOTICE, "Adding %s menu to app_confbridge\n", DEFAULT_MENU_PROFILE); + aco_set_defaults(&menu_type, DEFAULT_MENU_PROFILE, menu_profile); + ao2_link(cfg->menus, menu_profile); + } + return 0; } @@ -1951,6 +2077,7 @@ int conf_load_config(void) /* Menu options */ aco_option_register(&cfg_info, "type", ACO_EXACT, menu_types, NULL, OPT_NOOP_T, 0, 0); + aco_option_register_custom(&cfg_info, "template", ACO_EXACT, menu_types, NULL, menu_template_handler, 0); aco_option_register_custom(&cfg_info, "^[0-9A-D*#]+$", ACO_REGEX, menu_types, NULL, menu_option_handler, 0); if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) { @@ -1988,11 +2115,7 @@ const struct user_profile *conf_find_user_profile(struct ast_channel *chan, cons struct func_confbridge_data *b_data = NULL; RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); - if (!cfg) { - return NULL; - } - - if (chan) { + if (chan && ast_strlen_zero(user_profile_name)) { ast_channel_lock(chan); datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL); ast_channel_unlock(chan); @@ -2005,6 +2128,9 @@ const struct user_profile *conf_find_user_profile(struct ast_channel *chan, cons } } + if (!cfg) { + return NULL; + } if (ast_strlen_zero(user_profile_name)) { user_profile_name = DEFAULT_USER_PROFILE; } @@ -2042,11 +2168,7 @@ const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, struct func_confbridge_data *b_data = NULL; RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); - if (!cfg) { - return NULL; - } - - if (chan) { + if (chan && ast_strlen_zero(bridge_profile_name)) { ast_channel_lock(chan); datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL); ast_channel_unlock(chan); @@ -2058,6 +2180,10 @@ const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, } } } + + if (!cfg) { + return NULL; + } if (ast_strlen_zero(bridge_profile_name)) { bridge_profile_name = DEFAULT_BRIDGE_PROFILE; } @@ -2083,7 +2209,7 @@ static void menu_hook_destroy(void *hook_pvt) struct dtmf_menu_hook_pvt *pvt = hook_pvt; struct conf_menu_action *action = NULL; - ao2_ref(pvt->menu, -1); + ao2_cleanup(pvt->menu); while ((action = AST_LIST_REMOVE_HEAD(&pvt->menu_entry.actions, action))) { ast_free(action); @@ -2098,24 +2224,6 @@ static int menu_hook_callback(struct ast_bridge_channel *bridge_channel, void *h return conf_handle_dtmf(bridge_channel, pvt->user, &pvt->menu_entry, pvt->menu); } -static int copy_menu_entry(struct conf_menu_entry *dst, struct conf_menu_entry *src) -{ - struct conf_menu_action *menu_action = NULL; - struct conf_menu_action *new_menu_action = NULL; - - memcpy(dst, src, sizeof(*dst)); - AST_LIST_HEAD_INIT_NOLOCK(&dst->actions); - - AST_LIST_TRAVERSE(&src->actions, menu_action, action) { - if (!(new_menu_action = ast_calloc(1, sizeof(*new_menu_action)))) { - return -1; - } - memcpy(new_menu_action, menu_action, sizeof(*new_menu_action)); - AST_LIST_INSERT_HEAD(&dst->actions, new_menu_action, action); - } - return 0; -} - void conf_menu_entry_destroy(struct conf_menu_entry *menu_entry) { struct conf_menu_action *menu_action = NULL; @@ -2141,37 +2249,24 @@ int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu return 0; } -int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user) +static int apply_menu_hooks(struct confbridge_user *user, struct conf_menu *menu) { - struct conf_menu *menu; - struct conf_menu_entry *menu_entry = NULL; - RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); + struct conf_menu_entry *menu_entry; - if (!cfg) { - return -1; - } - - if (!(menu = menu_find(cfg->menus, menu_name))) { - return -1; - } - ao2_lock(menu); + SCOPED_AO2LOCK(menu_lock, menu); AST_LIST_TRAVERSE(&menu->entries, menu_entry, entry) { struct dtmf_menu_hook_pvt *pvt; if (!(pvt = ast_calloc(1, sizeof(*pvt)))) { - ao2_unlock(menu); - ao2_ref(menu, -1); - return -1; - } - if (copy_menu_entry(&pvt->menu_entry, menu_entry)) { - ast_free(pvt); - ao2_unlock(menu); - ao2_ref(menu, -1); return -1; } pvt->user = user; - ao2_ref(menu, +1); - pvt->menu = menu; + pvt->menu = ao2_bump(menu); + + if (copy_menu_entry(&pvt->menu_entry, menu_entry)) { + menu_hook_destroy(pvt); + return -1; + } if (ast_bridge_dtmf_hook(&user->features, pvt->menu_entry.dtmf, menu_hook_callback, pvt, menu_hook_destroy, 0)) { @@ -2179,12 +2274,47 @@ int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user) } } - ao2_unlock(menu); - ao2_ref(menu, -1); - return 0; } +int conf_set_menu_to_user(struct ast_channel *chan, struct confbridge_user *user, const char *menu_profile_name) +{ + RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); + RAII_VAR(struct conf_menu *, menu, NULL, ao2_cleanup); + + if (chan && ast_strlen_zero(menu_profile_name)) { + struct ast_datastore *datastore; + struct func_confbridge_data *b_data; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL); + ast_channel_unlock(chan); + if (datastore) { + /* If a menu exists in the CONFBRIDGE function datastore, use it. */ + b_data = datastore->data; + if (b_data->m_usable) { + menu = ao2_bump(b_data->menu); + return apply_menu_hooks(user, menu); + } + } + } + + /* Otherwise, we need to get whatever menu profile is specified to use (or default). */ + if (!cfg) { + return -1; + } + + if (ast_strlen_zero(menu_profile_name)) { + menu_profile_name = DEFAULT_MENU_PROFILE; + } + + if (!(menu = ao2_find(cfg->menus, menu_profile_name, OBJ_KEY))) { + return -1; + } + + return apply_menu_hooks(user, menu); +} + void conf_destroy_config(void) { ast_cli_unregister_multiple(cli_confbridge_parser, ARRAY_LEN(cli_confbridge_parser)); diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h index 71ae5f7de3..492cf85573 100644 --- a/apps/confbridge/include/confbridge.h +++ b/apps/confbridge/include/confbridge.h @@ -37,6 +37,7 @@ #define DEFAULT_USER_PROFILE "default_user" #define DEFAULT_BRIDGE_PROFILE "default_bridge" +#define DEFAULT_MENU_PROFILE "default_menu" #define DEFAULT_TALKING_THRESHOLD 160 #define DEFAULT_SILENCE_THRESHOLD 2500 @@ -260,31 +261,56 @@ void conf_destroy_config(void); * \brief find a user profile given a user profile's name and store * that profile in result structure. * - * \details This function first attempts to find any custom user - * profile that might exist on a channel datastore, if that doesn't - * exist it looks up the provided user profile name, if that doesn't - * exist either the default_user profile is used. - + * \param chan channel the user profile is requested for + * \param user_profile_name name of the profile requested (optional) + * \param result data contained by the user profile will be copied to this struct pointer + * + * \details If user_profile_name is not provided, this function will + * check for the presence of a user profile set by the CONFBRIDGE + * function on a channel datastore. If that doesn't exist, the + * default_user profile is used. + * * \retval user profile on success * \retval NULL on failure */ const struct user_profile *conf_find_user_profile(struct ast_channel *chan, const char *user_profile_name, struct user_profile *result); /*! - * \brief Find a bridge profile + * \brief Find a bridge profile given a bridge profile's name and store + * that profile in result structure. * - * \details Any bridge profile found using this function must be - * destroyed using conf_bridge_profile_destroy. This function first - * attempts to find any custom bridge profile that might exist on - * a channel datastore, if that doesn't exist it looks up the - * provided bridge profile name, if that doesn't exist either - * the default_bridge profile is used. + * \param chan channel the bridge profile is requested for + * \param bridge_profile_name name of the profile requested (optional) + * \param result data contained by the bridge profile will be copied to this struct pointer * - * \retval Bridge profile on success + * \details If bridge_profile_name is not provided, this function will + * check for the presence of a bridge profile set by the CONFBRIDGE + * function on a channel datastore. If that doesn't exist, the + * default_bridge profile is used. + * + * \retval bridge profile on success * \retval NULL on failure */ const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, const char *bridge_profile_name, struct bridge_profile *result); +/*! + * \brief find a menu profile given a menu profile's name and apply + * the menu in DTMF hooks. + * + * \param chan channel the menu profile is requested for + * \param user user profile the menu is being applied to + * \param menu_profile_name name of the profile requested (optional) + * + * \details If menu_profile_name is not provided, this function will + * check for the presence of a menu profile set by the CONFBRIDGE + * function on a channel datastore. If that doesn't exist, the + * default_menu profile is used. + * + * \retval 0 on success + * \retval -1 on failure + */ +int conf_set_menu_to_user(struct ast_channel *chan, struct confbridge_user *user, const char *menu_profile_name); + /*! * \brief Destroy a bridge profile found by 'conf_find_bridge_profile' */ @@ -296,14 +322,6 @@ void conf_bridge_profile_destroy(struct bridge_profile *b_profile); */ void conf_bridge_profile_copy(struct bridge_profile *dst, struct bridge_profile *src); -/*! - * \brief Set a DTMF menu to a conference user by menu name. - * - * \retval 0 on success, menu was found and set - * \retval -1 on error, menu was not found - */ -int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user); - /*! * \brief Finds a menu_entry in a menu structure matched by DTMF sequence. *