/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2011, Digium, Inc. * * David Vossel * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \brief ConfBridge config parser * * \author David Vossel */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/logger.h" #include "asterisk/config.h" #include "include/confbridge.h" #include "asterisk/astobj2.h" #include "asterisk/cli.h" #include "asterisk/bridging_features.h" #include "asterisk/stringfields.h" #include "asterisk/pbx.h" #define CONFBRIDGE_CONFIG "confbridge.conf" static struct ao2_container *user_profiles; static struct ao2_container *bridge_profiles; static struct ao2_container *menus; /*! bridge profile container functions */ static int bridge_cmp_cb(void *obj, void *arg, int flags) { const struct bridge_profile *entry1 = obj; const struct bridge_profile *entry2 = arg; return (!strcasecmp(entry1->name, entry2->name)) ? CMP_MATCH | CMP_STOP : 0; } static int bridge_hash_cb(const void *obj, const int flags) { const struct bridge_profile *b_profile = obj; return ast_str_case_hash(b_profile->name); } static int bridge_mark_delme_cb(void *obj, void *arg, int flag) { struct bridge_profile *entry = obj; entry->delme = 1; return 0; } static int match_bridge_delme_cb(void *obj, void *arg, int flag) { const struct bridge_profile *entry = obj; return entry->delme ? CMP_MATCH : 0; } /*! menu container functions */ static int menu_cmp_cb(void *obj, void *arg, int flags) { const struct conf_menu *entry1 = obj; const struct conf_menu *entry2 = arg; return (!strcasecmp(entry1->name, entry2->name)) ? CMP_MATCH | CMP_STOP : 0; } static int menu_hash_cb(const void *obj, const int flags) { const struct conf_menu *menu = obj; return ast_str_case_hash(menu->name); } static int menu_mark_delme_cb(void *obj, void *arg, int flag) { struct conf_menu *entry = obj; entry->delme = 1; return 0; } static int match_menu_delme_cb(void *obj, void *arg, int flag) { const struct conf_menu *entry = obj; return entry->delme ? CMP_MATCH : 0; } static void menu_destructor(void *obj) { struct conf_menu *menu = obj; struct conf_menu_entry *entry = NULL; while ((entry = AST_LIST_REMOVE_HEAD(&menu->entries, entry))) { conf_menu_entry_destroy(entry); ast_free(entry); } } /*! User profile container functions */ static int user_cmp_cb(void *obj, void *arg, int flags) { const struct user_profile *entry1 = obj; const struct user_profile *entry2 = arg; return (!strcasecmp(entry1->name, entry2->name)) ? CMP_MATCH | CMP_STOP : 0; } static int user_hash_cb(const void *obj, const int flags) { const struct user_profile *u_profile = obj; return ast_str_case_hash(u_profile->name); } static int user_mark_delme_cb(void *obj, void *arg, int flag) { struct user_profile *entry = obj; entry->delme = 1; return 0; } static int match_user_delme_cb(void *obj, void *arg, int flag) { const struct user_profile *entry = obj; return entry->delme ? CMP_MATCH : 0; } /*! Bridge Profile Sounds functions */ static void bridge_profile_sounds_destroy_cb(void *obj) { struct bridge_profile_sounds *sounds = obj; ast_string_field_free_memory(sounds); } static struct bridge_profile_sounds *bridge_profile_sounds_alloc(void) { struct bridge_profile_sounds *sounds = ao2_alloc(sizeof(*sounds), bridge_profile_sounds_destroy_cb); if (!sounds) { return NULL; } if (ast_string_field_init(sounds, 512)) { ao2_ref(sounds, -1); return NULL; } return sounds; } static int set_user_option(const char *name, const char *value, struct user_profile *u_profile) { if (!strcasecmp(name, "admin")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_ADMIN); } else if (!strcasecmp(name, "marked")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_MARKEDUSER); } else if (!strcasecmp(name, "startmuted")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_STARTMUTED); } else if (!strcasecmp(name, "music_on_hold_when_empty")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_MUSICONHOLD); } else if (!strcasecmp(name, "quiet")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_QUIET); } else if (!strcasecmp(name, "announce_user_count_all")) { if (ast_true(value)) { u_profile->flags = u_profile->flags | USER_OPT_ANNOUNCEUSERCOUNTALL; } else if (ast_false(value)) { u_profile->flags = u_profile->flags & ~USER_OPT_ANNOUNCEUSERCOUNTALL; } else if (sscanf(value, "%30u", &u_profile->announce_user_count_all_after) == 1) { u_profile->flags = u_profile->flags | USER_OPT_ANNOUNCEUSERCOUNTALL; } else { return -1; } } else if (!strcasecmp(name, "announce_user_count")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_ANNOUNCEUSERCOUNT); } else if (!strcasecmp(name, "announce_only_user")) { u_profile->flags = ast_true(value) ? u_profile->flags & ~USER_OPT_NOONLYPERSON : u_profile->flags | USER_OPT_NOONLYPERSON; } else if (!strcasecmp(name, "wait_marked")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_WAITMARKED); } else if (!strcasecmp(name, "end_marked")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_ENDMARKED); } else if (!strcasecmp(name, "talk_detection_events")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_TALKER_DETECT); } else if (!strcasecmp(name, "dtmf_passthrough")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_DTMF_PASS); } else if (!strcasecmp(name, "announce_join_leave")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_ANNOUNCE_JOIN_LEAVE); } else if (!strcasecmp(name, "pin")) { ast_copy_string(u_profile->pin, value, sizeof(u_profile->pin)); } else if (!strcasecmp(name, "music_on_hold_class")) { ast_copy_string(u_profile->moh_class, value, sizeof(u_profile->moh_class)); } else if (!strcasecmp(name, "announcement")) { ast_copy_string(u_profile->announcement, value, sizeof(u_profile->announcement)); } else if (!strcasecmp(name, "denoise")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_DENOISE); } else if (!strcasecmp(name, "dsp_talking_threshold")) { if (sscanf(value, "%30u", &u_profile->talking_threshold) != 1) { return -1; } } else if (!strcasecmp(name, "dsp_silence_threshold")) { if (sscanf(value, "%30u", &u_profile->silence_threshold) != 1) { return -1; } } else if (!strcasecmp(name, "dsp_drop_silence")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_DROP_SILENCE); } else if (!strcasecmp(name, "template")) { if (!(conf_find_user_profile(NULL, value, u_profile))) { return -1; } } else if (!strcasecmp(name, "jitterbuffer")) { ast_set2_flag(u_profile, ast_true(value), USER_OPT_JITTERBUFFER); } else { return -1; } return 0; } static int set_sound(const char *sound_name, const char *sound_file, struct bridge_profile_sounds *sounds) { if (ast_strlen_zero(sound_file)) { return -1; } if (!strcasecmp(sound_name, "sound_only_person")) { ast_string_field_set(sounds, onlyperson, sound_file); } else if (!strcasecmp(sound_name, "sound_only_one")) { ast_string_field_set(sounds, onlyone, sound_file); } else if (!strcasecmp(sound_name, "sound_has_joined")) { ast_string_field_set(sounds, hasjoin, sound_file); } else if (!strcasecmp(sound_name, "sound_has_left")) { ast_string_field_set(sounds, hasleft, sound_file); } else if (!strcasecmp(sound_name, "sound_kicked")) { ast_string_field_set(sounds, kicked, sound_file); } else if (!strcasecmp(sound_name, "sound_muted")) { ast_string_field_set(sounds, muted, sound_file); } else if (!strcasecmp(sound_name, "sound_unmuted")) { ast_string_field_set(sounds, unmuted, sound_file); } else if (!strcasecmp(sound_name, "sound_there_are")) { ast_string_field_set(sounds, thereare, sound_file); } else if (!strcasecmp(sound_name, "sound_other_in_party")) { ast_string_field_set(sounds, otherinparty, sound_file); } else if (!strcasecmp(sound_name, "sound_place_into_conference")) { ast_string_field_set(sounds, placeintoconf, sound_file); } else if (!strcasecmp(sound_name, "sound_wait_for_leader")) { ast_string_field_set(sounds, waitforleader, sound_file); } else if (!strcasecmp(sound_name, "sound_get_pin")) { ast_string_field_set(sounds, getpin, sound_file); } else if (!strcasecmp(sound_name, "sound_invalid_pin")) { ast_string_field_set(sounds, invalidpin, sound_file); } else if (!strcasecmp(sound_name, "sound_locked")) { ast_string_field_set(sounds, locked, sound_file); } else if (!strcasecmp(sound_name, "sound_unlocked_now")) { ast_string_field_set(sounds, unlockednow, sound_file); } else if (!strcasecmp(sound_name, "sound_locked_now")) { ast_string_field_set(sounds, lockednow, sound_file); } else if (!strcasecmp(sound_name, "sound_error_menu")) { ast_string_field_set(sounds, errormenu, sound_file); } else if (!strcasecmp(sound_name, "sound_join")) { ast_string_field_set(sounds, join, sound_file); } else if (!strcasecmp(sound_name, "sound_leave")) { ast_string_field_set(sounds, leave, sound_file); } else if (!strcasecmp(sound_name, "sound_participants_muted")) { ast_string_field_set(sounds, participantsmuted, sound_file); } else if (!strcasecmp(sound_name, "sound_participants_unmuted")) { ast_string_field_set(sounds, participantsunmuted, sound_file); } else { return -1; } return 0; } static int set_bridge_option(const char *name, const char *value, struct bridge_profile *b_profile) { if (!strcasecmp(name, "internal_sample_rate")) { if (!strcasecmp(value, "auto")) { b_profile->internal_sample_rate = 0; } else if (sscanf(value, "%30u", &b_profile->internal_sample_rate) != 1) { return -1; } } else if (!strcasecmp(name, "mixing_interval")) { if (sscanf(value, "%30u", &b_profile->mix_interval) != 1) { return -1; } switch (b_profile->mix_interval) { case 10: case 20: case 40: case 80: break; default: ast_log(LOG_WARNING, "invalid mixing interval %u\n", b_profile->mix_interval); b_profile->mix_interval = 0; return -1; } } else if (!strcasecmp(name, "record_conference")) { ast_set2_flag(b_profile, ast_true(value), BRIDGE_OPT_RECORD_CONFERENCE); } else if (!strcasecmp(name, "video_mode")) { if (!strcasecmp(value, "first_marked")) { ast_set_flag(b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED); } else if (!strcasecmp(value, "last_marked")) { ast_set_flag(b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED); } else if (!strcasecmp(value, "follow_talker")) { ast_set_flag(b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER); } } else if (!strcasecmp(name, "max_members")) { if (sscanf(value, "%30u", &b_profile->max_members) != 1) { return -1; } } else if (!strcasecmp(name, "record_file")) { ast_copy_string(b_profile->rec_file, value, sizeof(b_profile->rec_file)); } else if (strlen(name) >= 5 && !strncasecmp(name, "sound", 5)) { if (set_sound(name, value, b_profile->sounds)) { return -1; } } else if (!strcasecmp(name, "template")) { /* Only documented for use in CONFBRIDGE dialplan function */ struct bridge_profile *tmp = b_profile; struct bridge_profile_sounds *sounds = bridge_profile_sounds_alloc(); struct bridge_profile_sounds *oldsounds = b_profile->sounds; if (!sounds) { return -1; } if (!(conf_find_bridge_profile(NULL, value, tmp))) { ao2_ref(sounds, -1); return -1; } /* Using a bridge profile as a template is a little complicated due to the sounds. Since the sounds * structure of a dynamic profile will need to be altered, a completely new sounds structure must be * created instead of simply holding a reference to the one built by the config file. */ ast_string_field_set(sounds, onlyperson, tmp->sounds->onlyperson); ast_string_field_set(sounds, hasjoin, tmp->sounds->hasjoin); ast_string_field_set(sounds, hasleft, tmp->sounds->hasleft); ast_string_field_set(sounds, kicked, tmp->sounds->kicked); ast_string_field_set(sounds, muted, tmp->sounds->muted); ast_string_field_set(sounds, unmuted, tmp->sounds->unmuted); ast_string_field_set(sounds, thereare, tmp->sounds->thereare); ast_string_field_set(sounds, otherinparty, tmp->sounds->otherinparty); ast_string_field_set(sounds, placeintoconf, tmp->sounds->placeintoconf); ast_string_field_set(sounds, waitforleader, tmp->sounds->waitforleader); ast_string_field_set(sounds, getpin, tmp->sounds->getpin); ast_string_field_set(sounds, invalidpin, tmp->sounds->invalidpin); ast_string_field_set(sounds, locked, tmp->sounds->locked); ast_string_field_set(sounds, unlockednow, tmp->sounds->unlockednow); ast_string_field_set(sounds, lockednow, tmp->sounds->lockednow); ast_string_field_set(sounds, errormenu, tmp->sounds->errormenu); ast_string_field_set(sounds, participantsmuted, tmp->sounds->participantsmuted); ast_string_field_set(sounds, participantsunmuted, tmp->sounds->participantsunmuted); ao2_ref(tmp->sounds, -1); /* sounds struct copied over to it from the template by reference only. */ ao2_ref(oldsounds,-1); /* original sounds struct we don't need anymore */ tmp->sounds = sounds; /* the new sounds struct that is a deep copy of the one from the template. */ } else { return -1; } return 0; } /*! CONFBRIDGE dialplan function functions and channel datastore. */ struct func_confbridge_data { struct bridge_profile b_profile; struct user_profile u_profile; 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 */ }; 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); }; static const struct ast_datastore_info confbridge_datastore = { .type = "confbridge", .destroy = func_confbridge_destroy_cb }; int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value) { struct ast_datastore *datastore = NULL; struct func_confbridge_data *b_data = NULL; char *parse = NULL; int new = 0; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(type); AST_APP_ARG(option); ); /* parse all the required arguments and make sure they exist. */ if (ast_strlen_zero(data) || ast_strlen_zero(value)) { return -1; } parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); if (ast_strlen_zero(args.type) || ast_strlen_zero(args.option)) { return -1; } ast_channel_lock(chan); if (!(datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL))) { ast_channel_unlock(chan); if (!(datastore = ast_datastore_alloc(&confbridge_datastore, NULL))) { return 0; } if (!(b_data = ast_calloc(1, sizeof(*b_data)))) { ast_datastore_free(datastore); return 0; } if (!(b_data->b_profile.sounds = bridge_profile_sounds_alloc())) { ast_datastore_free(datastore); ast_free(b_data); return 0; } datastore->data = b_data; new = 1; } else { ast_channel_unlock(chan); b_data = datastore->data; } /* SET(CONFBRIDGE(type,option)=value) */ if (!strcasecmp(args.type, "bridge") && !set_bridge_option(args.option, value, &b_data->b_profile)) { b_data->b_usable = 1; } else if (!strcasecmp(args.type, "user") && !set_user_option(args.option, value, &b_data->u_profile)) { b_data->u_usable = 1; } else { ast_log(LOG_WARNING, "Profile type \"%s\" can not be set in CONFBRIDGE function with option \"%s\" and value \"%s\"\n", args.type, args.option, value); goto cleanup_error; } if (new) { ast_channel_lock(chan); ast_channel_datastore_add(chan, datastore); ast_channel_unlock(chan); } return 0; cleanup_error: ast_log(LOG_ERROR, "Invalid argument provided to the %s function\n", cmd); if (new) { ast_datastore_free(datastore); } return -1; } /*! * \brief Parse the bridge profile options */ static int parse_bridge(const char *cat, struct ast_config *cfg) { struct ast_variable *var; struct bridge_profile tmp; struct bridge_profile *b_profile; ast_copy_string(tmp.name, cat, sizeof(tmp.name)); if ((b_profile = ao2_find(bridge_profiles, &tmp, OBJ_POINTER))) { b_profile->delme = 0; } else if ((b_profile = ao2_alloc(sizeof(*b_profile), NULL))) { ast_copy_string(b_profile->name, cat, sizeof(b_profile->name)); ao2_link(bridge_profiles, b_profile); } else { return -1; } ao2_lock(b_profile); /* set defaults */ b_profile->internal_sample_rate = 0; b_profile->flags = 0; b_profile->max_members = 0; b_profile->mix_interval = 0; memset(b_profile->rec_file, 0, sizeof(b_profile->rec_file)); if (b_profile->sounds) { ao2_ref(b_profile->sounds, -1); /* sounds is read only. Once it has been created * it can never be altered. This prevents having to * do any locking after it is built from the config. */ b_profile->sounds = NULL; } if (!(b_profile->sounds = bridge_profile_sounds_alloc())) { ao2_unlock(b_profile); ao2_ref(b_profile, -1); ao2_unlink(bridge_profiles, b_profile); return -1; } for (var = ast_variable_browse(cfg, cat); var; var = var->next) { if (!strcasecmp(var->name, "type")) { continue; } else if (set_bridge_option(var->name, var->value, b_profile)) { ast_log(LOG_WARNING, "Invalid: '%s' at line %d of %s is not supported.\n", var->name, var->lineno, CONFBRIDGE_CONFIG); } } ao2_unlock(b_profile); ao2_ref(b_profile, -1); return 0; } static int parse_user(const char *cat, struct ast_config *cfg) { struct ast_variable *var; struct user_profile tmp; struct user_profile *u_profile; ast_copy_string(tmp.name, cat, sizeof(tmp.name)); if ((u_profile = ao2_find(user_profiles, &tmp, OBJ_POINTER))) { u_profile->delme = 0; } else if ((u_profile = ao2_alloc(sizeof(*u_profile), NULL))) { ast_copy_string(u_profile->name, cat, sizeof(u_profile->name)); ao2_link(user_profiles, u_profile); } else { return -1; } ao2_lock(u_profile); /* set defaults */ u_profile->flags = 0; u_profile->announce_user_count_all_after = 0; u_profile->silence_threshold = DEFAULT_SILENCE_THRESHOLD; u_profile->talking_threshold = DEFAULT_TALKING_THRESHOLD; memset(u_profile->pin, 0, sizeof(u_profile->pin)); memset(u_profile->moh_class, 0, sizeof(u_profile->moh_class)); memset(u_profile->announcement, 0, sizeof(u_profile->announcement)); for (var = ast_variable_browse(cfg, cat); var; var = var->next) { if (!strcasecmp(var->name, "type")) { continue; } else if (set_user_option(var->name, var->value, u_profile)) { ast_log(LOG_WARNING, "Invalid option '%s' at line %d of %s is not supported.\n", var->name, var->lineno, CONFBRIDGE_CONFIG); } } ao2_unlock(u_profile); ao2_ref(u_profile, -1); return 0; } static int add_action_to_menu_entry(struct conf_menu_entry *menu_entry, enum conf_menu_action_id id, char *databuf) { struct conf_menu_action *menu_action = ast_calloc(1, sizeof(*menu_action)); if (!menu_action) { return -1; } menu_action->id = id; switch (id) { case MENU_ACTION_NOOP: case MENU_ACTION_TOGGLE_MUTE: case MENU_ACTION_INCREASE_LISTENING: case MENU_ACTION_DECREASE_LISTENING: case MENU_ACTION_INCREASE_TALKING: case MENU_ACTION_DECREASE_TALKING: case MENU_ACTION_RESET_LISTENING: case MENU_ACTION_RESET_TALKING: case MENU_ACTION_ADMIN_TOGGLE_LOCK: case MENU_ACTION_ADMIN_TOGGLE_MUTE_PARTICIPANTS: case MENU_ACTION_PARTICIPANT_COUNT: case MENU_ACTION_ADMIN_KICK_LAST: case MENU_ACTION_LEAVE: case MENU_ACTION_SET_SINGLE_VIDEO_SRC: case MENU_ACTION_RELEASE_SINGLE_VIDEO_SRC: break; case MENU_ACTION_PLAYBACK: case MENU_ACTION_PLAYBACK_AND_CONTINUE: if (!(ast_strlen_zero(databuf))) { ast_copy_string(menu_action->data.playback_file, databuf, sizeof(menu_action->data.playback_file)); } else { ast_free(menu_action); return -1; } break; case MENU_ACTION_DIALPLAN_EXEC: if (!(ast_strlen_zero(databuf))) { AST_DECLARE_APP_ARGS(args, AST_APP_ARG(context); AST_APP_ARG(exten); AST_APP_ARG(priority); ); AST_STANDARD_APP_ARGS(args, databuf); if (!ast_strlen_zero(args.context)) { ast_copy_string(menu_action->data.dialplan_args.context, args.context, sizeof(menu_action->data.dialplan_args.context)); } if (!ast_strlen_zero(args.exten)) { ast_copy_string(menu_action->data.dialplan_args.exten, args.exten, sizeof(menu_action->data.dialplan_args.exten)); } menu_action->data.dialplan_args.priority = 1; /* 1 by default */ if (!ast_strlen_zero(args.priority) && (sscanf(args.priority, "%30u", &menu_action->data.dialplan_args.priority) != 1)) { /* invalid priority */ ast_free(menu_action); return -1; } } else { ast_free(menu_action); return -1; } }; AST_LIST_INSERT_TAIL(&menu_entry->actions, menu_action, action); return 0; } static int add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names) { struct conf_menu_entry *menu_entry = NULL, *cur = NULL; int res = 0; char *tmp_action_names = ast_strdupa(action_names); char *action = NULL; char *action_args; char *tmp; char buf[PATH_MAX]; char *delimiter = ","; if (!(menu_entry = ast_calloc(1, sizeof(*menu_entry)))) { return -1; } for (;;) { char *comma; char *startbrace; char *endbrace; unsigned int action_len; if (ast_strlen_zero(tmp_action_names)) { break; } startbrace = strchr(tmp_action_names, '('); endbrace = strchr(tmp_action_names, ')'); comma = strchr(tmp_action_names, ','); /* If the next action has brackets with comma delimited arguments in it, * make the delimeter ')' instead of a comma to preserve the argments */ if (startbrace && endbrace && comma && (comma > startbrace && comma < endbrace)) { delimiter = ")"; } else { delimiter = ","; } if (!(action = strsep(&tmp_action_names, delimiter))) { break; } action = ast_strip(action); if (ast_strlen_zero(action)) { continue; } action_len = strlen(action); ast_copy_string(menu_entry->dtmf, dtmf, sizeof(menu_entry->dtmf)); if (!strcasecmp(action, "toggle_mute")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_TOGGLE_MUTE, NULL); } else if (!strcasecmp(action, "no_op")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_NOOP, NULL); } else if (!strcasecmp(action, "increase_listening_volume")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_INCREASE_LISTENING, NULL); } else if (!strcasecmp(action, "decrease_listening_volume")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_DECREASE_LISTENING, NULL); } else if (!strcasecmp(action, "increase_talking_volume")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_INCREASE_TALKING, NULL); } else if (!strcasecmp(action, "reset_listening_volume")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_RESET_LISTENING, NULL); } else if (!strcasecmp(action, "reset_talking_volume")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_RESET_TALKING, NULL); } else if (!strcasecmp(action, "decrease_talking_volume")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_DECREASE_TALKING, NULL); } else if (!strcasecmp(action, "admin_toggle_conference_lock")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_ADMIN_TOGGLE_LOCK, NULL); } else if (!strcasecmp(action, "admin_toggle_mute_participants")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_ADMIN_TOGGLE_MUTE_PARTICIPANTS, NULL); } else if (!strcasecmp(action, "participant_count")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_PARTICIPANT_COUNT, NULL); } else if (!strcasecmp(action, "admin_kick_last")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_ADMIN_KICK_LAST, NULL); } else if (!strcasecmp(action, "leave_conference")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_LEAVE, NULL); } else if (!strcasecmp(action, "set_as_single_video_src")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_SET_SINGLE_VIDEO_SRC, NULL); } else if (!strcasecmp(action, "release_as_single_video_src")) { res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_RELEASE_SINGLE_VIDEO_SRC, NULL); } else if (!strncasecmp(action, "dialplan_exec(", 14)) { ast_copy_string(buf, action, sizeof(buf)); action_args = buf; if ((action_args = strchr(action, '('))) { action_args++; } /* it is possible that this argument may or may not * have a closing brace at this point, it all depends on if * comma delimited arguments were provided */ if ((tmp = strchr(action, ')'))) { *tmp = '\0'; } res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_DIALPLAN_EXEC, action_args); } else if (action_len >= 21 && !strncasecmp(action, "playback_and_continue(", 22)) { ast_copy_string(buf, action, sizeof(buf)); action_args = buf; if ((action_args = strchr(action, '(')) && (tmp = strrchr(action_args, ')'))) { *tmp = '\0'; action_args++; } res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_PLAYBACK_AND_CONTINUE, action_args); } else if (action_len >= 8 && !strncasecmp(action, "playback(", 9)) { ast_copy_string(buf, action, sizeof(buf)); action_args = buf; if ((action_args = strchr(action, '(')) && (tmp = strrchr(action_args, ')'))) { *tmp = '\0'; action_args++; } res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_PLAYBACK, action_args); } } /* if adding any of the actions failed, bail */ if (res) { struct conf_menu_action *menu_action; while ((menu_action = AST_LIST_REMOVE_HEAD(&menu_entry->actions, action))) { ast_free(menu_action); } ast_free(menu_entry); return -1; } /* remove any list entry with an identical DTMF sequence for overrides */ AST_LIST_TRAVERSE_SAFE_BEGIN(&menu->entries, cur, entry) { if (!strcasecmp(cur->dtmf, menu_entry->dtmf)) { AST_LIST_REMOVE_CURRENT(entry); ast_free(cur); break; } } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_INSERT_TAIL(&menu->entries, menu_entry, entry); return 0; } static int parse_menu(const char *cat, struct ast_config *cfg) { struct ast_variable *var; struct conf_menu tmp; struct conf_menu *menu; ast_copy_string(tmp.name, cat, sizeof(tmp.name)); if ((menu = ao2_find(menus, &tmp, OBJ_POINTER))) { menu->delme = 0; } else if ((menu = ao2_alloc(sizeof(*menu), menu_destructor))) { ast_copy_string(menu->name, cat, sizeof(menu->name)); ao2_link(menus, menu); } else { return -1; } ao2_lock(menu); /* this isn't freeing the menu, just destroying the menu list so it can be rebuilt.*/ menu_destructor(menu); for (var = ast_variable_browse(cfg, cat); var; var = var->next) { if (!strcasecmp(var->name, "type")) { continue; } else if (add_menu_entry(menu, var->name, var->value)) { ast_log(LOG_WARNING, "Unknown option '%s' at line %d of %s is not supported.\n", var->name, var->lineno, CONFBRIDGE_CONFIG); } } ao2_unlock(menu); ao2_ref(menu, -1); return 0; } static char *complete_user_profile_name(const char *line, const char *word, int pos, int state) { int which = 0; char *res = NULL; int wordlen = strlen(word); struct ao2_iterator i; struct user_profile *u_profile = NULL; i = ao2_iterator_init(user_profiles, 0); while ((u_profile = ao2_iterator_next(&i))) { if (!strncasecmp(u_profile->name, word, wordlen) && ++which > state) { res = ast_strdup(u_profile->name); ao2_ref(u_profile, -1); break; } ao2_ref(u_profile, -1); } ao2_iterator_destroy(&i); return res; } static char *handle_cli_confbridge_show_user_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_iterator it; struct user_profile *u_profile; switch (cmd) { case CLI_INIT: e->command = "confbridge show profile users"; e->usage = "Usage confbridge show profile users\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd,"--------- User Profiles -----------\n"); ao2_lock(user_profiles); it = ao2_iterator_init(user_profiles, 0); while ((u_profile = ao2_iterator_next(&it))) { ast_cli(a->fd,"%s\n", u_profile->name); ao2_ref(u_profile, -1); } ao2_iterator_destroy(&it); ao2_unlock(user_profiles); return CLI_SUCCESS; } static char *handle_cli_confbridge_show_user_profile(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct user_profile u_profile; switch (cmd) { case CLI_INIT: e->command = "confbridge show profile user"; e->usage = "Usage confbridge show profile user []\n"; return NULL; case CLI_GENERATE: if (a->pos == 4) { return complete_user_profile_name(a->line, a->word, a->pos, a->n); } return NULL; } if (a->argc != 5) { return CLI_SHOWUSAGE; } if (!(conf_find_user_profile(NULL, a->argv[4], &u_profile))) { ast_cli(a->fd, "No conference user profile named '%s' found!\n", a->argv[4]); return CLI_SUCCESS; } ast_cli(a->fd,"--------------------------------------------\n"); ast_cli(a->fd,"Name: %s\n", u_profile.name); ast_cli(a->fd,"Admin: %s\n", u_profile.flags & USER_OPT_ADMIN ? "true" : "false"); ast_cli(a->fd,"Marked User: %s\n", u_profile.flags & USER_OPT_MARKEDUSER ? "true" : "false"); ast_cli(a->fd,"Start Muted: %s\n", u_profile.flags & USER_OPT_STARTMUTED? "true" : "false"); ast_cli(a->fd,"MOH When Empty: %s\n", u_profile.flags & USER_OPT_MUSICONHOLD ? "enabled" : "disabled"); ast_cli(a->fd,"MOH Class: %s\n", ast_strlen_zero(u_profile.moh_class) ? "default" : u_profile.moh_class); ast_cli(a->fd,"Announcement: %s\n", u_profile.announcement); ast_cli(a->fd,"Quiet: %s\n", u_profile.flags & USER_OPT_QUIET ? "enabled" : "disabled"); ast_cli(a->fd,"Wait Marked: %s\n", u_profile.flags & USER_OPT_WAITMARKED ? "enabled" : "disabled"); ast_cli(a->fd,"END Marked: %s\n", u_profile.flags & USER_OPT_ENDMARKED ? "enabled" : "disabled"); ast_cli(a->fd,"Drop_silence: %s\n", u_profile.flags & USER_OPT_DROP_SILENCE ? "enabled" : "disabled"); ast_cli(a->fd,"Silence Threshold: %dms\n", u_profile.silence_threshold); ast_cli(a->fd,"Talking Threshold: %dms\n", u_profile.talking_threshold); ast_cli(a->fd,"Denoise: %s\n", u_profile.flags & USER_OPT_DENOISE ? "enabled" : "disabled"); ast_cli(a->fd,"Jitterbuffer: %s\n", u_profile.flags & USER_OPT_JITTERBUFFER ? "enabled" : "disabled"); ast_cli(a->fd,"Talk Detect Events: %s\n", u_profile.flags & USER_OPT_TALKER_DETECT ? "enabled" : "disabled"); ast_cli(a->fd,"DTMF Pass Through: %s\n", u_profile.flags & USER_OPT_DTMF_PASS ? "enabled" : "disabled"); ast_cli(a->fd,"PIN: %s\n", ast_strlen_zero(u_profile.pin) ? "None" : u_profile.pin); ast_cli(a->fd,"Announce User Count: %s\n", u_profile.flags & USER_OPT_ANNOUNCEUSERCOUNT ? "enabled" : "disabled"); ast_cli(a->fd,"Announce join/leave: %s\n", u_profile.flags & USER_OPT_ANNOUNCE_JOIN_LEAVE ? "enabled" : "disabled"); ast_cli(a->fd,"Announce User Count all: %s\n", u_profile.flags & USER_OPT_ANNOUNCEUSERCOUNTALL ? "enabled" : "disabled"); ast_cli(a->fd,"\n"); return CLI_SUCCESS; } static char *complete_bridge_profile_name(const char *line, const char *word, int pos, int state) { int which = 0; char *res = NULL; int wordlen = strlen(word); struct ao2_iterator i; struct bridge_profile *b_profile = NULL; i = ao2_iterator_init(bridge_profiles, 0); while ((b_profile = ao2_iterator_next(&i))) { if (!strncasecmp(b_profile->name, word, wordlen) && ++which > state) { res = ast_strdup(b_profile->name); ao2_ref(b_profile, -1); break; } ao2_ref(b_profile, -1); } ao2_iterator_destroy(&i); return res; } static char *handle_cli_confbridge_show_bridge_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_iterator it; struct bridge_profile *b_profile; switch (cmd) { case CLI_INIT: e->command = "confbridge show profile bridges"; e->usage = "Usage confbridge show profile bridges\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd,"--------- Bridge Profiles -----------\n"); ao2_lock(bridge_profiles); it = ao2_iterator_init(bridge_profiles, 0); while ((b_profile = ao2_iterator_next(&it))) { ast_cli(a->fd,"%s\n", b_profile->name); ao2_ref(b_profile, -1); } ao2_iterator_destroy(&it); ao2_unlock(bridge_profiles); return CLI_SUCCESS; } static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct bridge_profile b_profile; char tmp[64]; switch (cmd) { case CLI_INIT: e->command = "confbridge show profile bridge"; e->usage = "Usage confbridge show profile bridge \n"; return NULL; case CLI_GENERATE: if (a->pos == 4) { return complete_bridge_profile_name(a->line, a->word, a->pos, a->n); } return NULL; } if (a->argc != 5) { return CLI_SHOWUSAGE; } if (!(conf_find_bridge_profile(NULL, a->argv[4], &b_profile))) { ast_cli(a->fd, "No conference bridge profile named '%s' found!\n", a->argv[4]); return CLI_SUCCESS; } ast_cli(a->fd,"--------------------------------------------\n"); ast_cli(a->fd,"Name: %s\n", b_profile.name); if (b_profile.internal_sample_rate) { snprintf(tmp, sizeof(tmp), "%d", b_profile.internal_sample_rate); } else { ast_copy_string(tmp, "auto", sizeof(tmp)); } ast_cli(a->fd,"Internal Sample Rate: %s\n", tmp); if (b_profile.mix_interval) { ast_cli(a->fd,"Mixing Interval: %d\n", b_profile.mix_interval); } else { ast_cli(a->fd,"Mixing Interval: Default 20ms\n"); } ast_cli(a->fd,"Record Conference: %s\n", b_profile.flags & BRIDGE_OPT_RECORD_CONFERENCE ? "yes" : "no"); ast_cli(a->fd,"Record File: %s\n", ast_strlen_zero(b_profile.rec_file) ? "Auto Generated" : b_profile.rec_file); if (b_profile.max_members) { ast_cli(a->fd,"Max Members: %d\n", b_profile.max_members); } else { ast_cli(a->fd,"Max Members: No Limit\n"); } if (b_profile.flags & BRIDGE_OPT_VIDEO_SRC_LAST_MARKED) { ast_cli(a->fd, "Video Mode: last_marked\n"); } else if (b_profile.flags & BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED) { ast_cli(a->fd, "Video Mode: first_marked\n"); } else if (b_profile.flags & BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER) { ast_cli(a->fd, "Video Mode: follow_talker\n"); } else { ast_cli(a->fd, "Video Mode: no video\n"); } ast_cli(a->fd,"sound_join: %s\n", conf_get_sound(CONF_SOUND_JOIN, b_profile.sounds)); ast_cli(a->fd,"sound_leave: %s\n", conf_get_sound(CONF_SOUND_LEAVE, b_profile.sounds)); ast_cli(a->fd,"sound_only_person: %s\n", conf_get_sound(CONF_SOUND_ONLY_PERSON, b_profile.sounds)); ast_cli(a->fd,"sound_has_joined: %s\n", conf_get_sound(CONF_SOUND_HAS_JOINED, b_profile.sounds)); ast_cli(a->fd,"sound_has_left: %s\n", conf_get_sound(CONF_SOUND_HAS_LEFT, b_profile.sounds)); ast_cli(a->fd,"sound_kicked: %s\n", conf_get_sound(CONF_SOUND_KICKED, b_profile.sounds)); ast_cli(a->fd,"sound_muted: %s\n", conf_get_sound(CONF_SOUND_MUTED, b_profile.sounds)); ast_cli(a->fd,"sound_unmuted: %s\n", conf_get_sound(CONF_SOUND_UNMUTED, b_profile.sounds)); ast_cli(a->fd,"sound_there_are: %s\n", conf_get_sound(CONF_SOUND_THERE_ARE, b_profile.sounds)); ast_cli(a->fd,"sound_other_in_party: %s\n", conf_get_sound(CONF_SOUND_OTHER_IN_PARTY, b_profile.sounds)); ast_cli(a->fd,"sound_place_into_conference: %s\n", conf_get_sound(CONF_SOUND_PLACE_IN_CONF, b_profile.sounds)); ast_cli(a->fd,"sound_wait_for_leader: %s\n", conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, b_profile.sounds)); ast_cli(a->fd,"sound_get_pin: %s\n", conf_get_sound(CONF_SOUND_GET_PIN, b_profile.sounds)); ast_cli(a->fd,"sound_invalid_pin: %s\n", conf_get_sound(CONF_SOUND_INVALID_PIN, b_profile.sounds)); ast_cli(a->fd,"sound_locked: %s\n", conf_get_sound(CONF_SOUND_LOCKED, b_profile.sounds)); ast_cli(a->fd,"sound_unlocked_now: %s\n", conf_get_sound(CONF_SOUND_UNLOCKED_NOW, b_profile.sounds)); ast_cli(a->fd,"sound_lockednow: %s\n", conf_get_sound(CONF_SOUND_LOCKED_NOW, b_profile.sounds)); ast_cli(a->fd,"sound_error_menu: %s\n", conf_get_sound(CONF_SOUND_ERROR_MENU, b_profile.sounds)); ast_cli(a->fd,"sound_participants_muted: %s\n", conf_get_sound(CONF_SOUND_PARTICIPANTS_MUTED, b_profile.sounds)); ast_cli(a->fd,"sound_participants_unmuted: %s\n", conf_get_sound(CONF_SOUND_PARTICIPANTS_UNMUTED, b_profile.sounds)); ast_cli(a->fd,"\n"); conf_bridge_profile_destroy(&b_profile); return CLI_SUCCESS; } static char *complete_menu_name(const char *line, const char *word, int pos, int state) { int which = 0; char *res = NULL; int wordlen = strlen(word); struct ao2_iterator i; struct conf_menu *menu = NULL; i = ao2_iterator_init(menus, 0); while ((menu = ao2_iterator_next(&i))) { if (!strncasecmp(menu->name, word, wordlen) && ++which > state) { res = ast_strdup(menu->name); ao2_ref(menu, -1); break; } ao2_ref(menu, -1); } ao2_iterator_destroy(&i); return res; } static char *handle_cli_confbridge_show_menus(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_iterator it; struct conf_menu *menu; switch (cmd) { case CLI_INIT: e->command = "confbridge show menus"; e->usage = "Usage confbridge show profile menus\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd,"--------- Menus -----------\n"); ao2_lock(menus); it = ao2_iterator_init(menus, 0); while ((menu = ao2_iterator_next(&it))) { ast_cli(a->fd,"%s\n", menu->name); ao2_ref(menu, -1); } ao2_iterator_destroy(&it); ao2_unlock(menus); return CLI_SUCCESS; } static char *handle_cli_confbridge_show_menu(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct conf_menu tmp; struct conf_menu *menu; struct conf_menu_entry *menu_entry = NULL; struct conf_menu_action *menu_action = NULL; switch (cmd) { case CLI_INIT: e->command = "confbridge show menu"; e->usage = "Usage confbridge show menu []\n"; return NULL; case CLI_GENERATE: if (a->pos == 3) { return complete_menu_name(a->line, a->word, a->pos, a->n); } return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } ast_copy_string(tmp.name, a->argv[3], sizeof(tmp.name)); if (!(menu = ao2_find(menus, &tmp, OBJ_POINTER))) { ast_cli(a->fd, "No conference menu named '%s' found!\n", a->argv[3]); return CLI_SUCCESS; } ao2_lock(menu); ast_cli(a->fd,"Name: %s\n", menu->name); AST_LIST_TRAVERSE(&menu->entries, menu_entry, entry) { int action_num = 0; ast_cli(a->fd, "%s=", menu_entry->dtmf); AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) { if (action_num) { ast_cli(a->fd, ", "); } switch (menu_action->id) { case MENU_ACTION_TOGGLE_MUTE: ast_cli(a->fd, "toggle_mute"); break; case MENU_ACTION_NOOP: ast_cli(a->fd, "no_op"); break; case MENU_ACTION_INCREASE_LISTENING: ast_cli(a->fd, "increase_listening_volume"); break; case MENU_ACTION_DECREASE_LISTENING: ast_cli(a->fd, "decrease_listening_volume"); break; case MENU_ACTION_RESET_LISTENING: ast_cli(a->fd, "reset_listening_volume"); break; case MENU_ACTION_RESET_TALKING: ast_cli(a->fd, "reset_talking_volume"); break; case MENU_ACTION_INCREASE_TALKING: ast_cli(a->fd, "increase_talking_volume"); break; case MENU_ACTION_DECREASE_TALKING: ast_cli(a->fd, "decrease_talking_volume"); break; case MENU_ACTION_PLAYBACK: ast_cli(a->fd, "playback(%s)", menu_action->data.playback_file); break; case MENU_ACTION_PLAYBACK_AND_CONTINUE: ast_cli(a->fd, "playback_and_continue(%s)", menu_action->data.playback_file); break; case MENU_ACTION_DIALPLAN_EXEC: ast_cli(a->fd, "dialplan_exec(%s,%s,%d)", menu_action->data.dialplan_args.context, menu_action->data.dialplan_args.exten, menu_action->data.dialplan_args.priority); break; case MENU_ACTION_ADMIN_TOGGLE_LOCK: ast_cli(a->fd, "admin_toggle_conference_lock"); break; case MENU_ACTION_ADMIN_TOGGLE_MUTE_PARTICIPANTS: ast_cli(a->fd, "admin_toggle_mute_participants"); break; case MENU_ACTION_PARTICIPANT_COUNT: ast_cli(a->fd, "participant_count"); break; case MENU_ACTION_ADMIN_KICK_LAST: ast_cli(a->fd, "admin_kick_last"); break; case MENU_ACTION_LEAVE: ast_cli(a->fd, "leave_conference"); break; case MENU_ACTION_SET_SINGLE_VIDEO_SRC: ast_cli(a->fd, "set_as_single_video_src"); break; case MENU_ACTION_RELEASE_SINGLE_VIDEO_SRC: ast_cli(a->fd, "release_as_single_video_src"); break; } action_num++; } ast_cli(a->fd,"\n"); } ao2_unlock(menu); ao2_ref(menu, -1); return CLI_SUCCESS; } static struct ast_cli_entry cli_confbridge_parser[] = { AST_CLI_DEFINE(handle_cli_confbridge_show_user_profile, "Show a conference user profile."), AST_CLI_DEFINE(handle_cli_confbridge_show_bridge_profile, "Show a conference bridge profile."), AST_CLI_DEFINE(handle_cli_confbridge_show_menu, "Show a conference menu"), AST_CLI_DEFINE(handle_cli_confbridge_show_user_profiles, "Show a list of conference user profiles."), AST_CLI_DEFINE(handle_cli_confbridge_show_bridge_profiles, "Show a list of conference bridge profiles."), AST_CLI_DEFINE(handle_cli_confbridge_show_menus, "Show a list of conference menus"), }; static int conf_parse_init(void) { if (!(user_profiles = ao2_container_alloc(283, user_hash_cb, user_cmp_cb))) { conf_destroy_config(); return -1; } if (!(bridge_profiles = ao2_container_alloc(283, bridge_hash_cb, bridge_cmp_cb))) { conf_destroy_config(); return -1; } if (!(menus = ao2_container_alloc(283, menu_hash_cb, menu_cmp_cb))) { conf_destroy_config(); return -1; } ast_cli_register_multiple(cli_confbridge_parser, ARRAY_LEN(cli_confbridge_parser)); return 0; } void conf_destroy_config() { if (user_profiles) { ao2_ref(user_profiles, -1); user_profiles = NULL; } if (bridge_profiles) { ao2_ref(bridge_profiles, -1); bridge_profiles = NULL; } if (menus) { ao2_ref(menus, -1); menus = NULL; } ast_cli_unregister_multiple(cli_confbridge_parser, sizeof(cli_confbridge_parser) / sizeof(struct ast_cli_entry)); } static void remove_all_delme(void) { ao2_callback(user_profiles, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, match_user_delme_cb, NULL); ao2_callback(bridge_profiles, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, match_bridge_delme_cb, NULL); ao2_callback(menus, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, match_menu_delme_cb, NULL); } static void mark_all_delme(void) { ao2_callback(user_profiles, OBJ_NODATA | OBJ_MULTIPLE, user_mark_delme_cb, NULL); ao2_callback(bridge_profiles, OBJ_NODATA | OBJ_MULTIPLE, bridge_mark_delme_cb, NULL); ao2_callback(menus, OBJ_NODATA | OBJ_MULTIPLE, menu_mark_delme_cb, NULL); } int conf_load_config(int reload) { struct ast_flags config_flags = { 0, }; struct ast_config *cfg = ast_config_load(CONFBRIDGE_CONFIG, config_flags); const char *type = NULL; char *cat = NULL; if (!reload) { conf_parse_init(); } if (!cfg || cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { return 0; } mark_all_delme(); while ((cat = ast_category_browse(cfg, cat))) { if (!(type = (ast_variable_retrieve(cfg, cat, "type")))) { if (strcasecmp(cat, "general")) { ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat); } continue; } if (!strcasecmp(type, "bridge")) { parse_bridge(cat, cfg); } else if (!strcasecmp(type, "user")) { parse_user(cat, cfg); } else if (!strcasecmp(type, "menu")) { parse_menu(cat, cfg); } else { continue; } } remove_all_delme(); ast_config_destroy(cfg); return 0; } static void conf_user_profile_copy(struct user_profile *dst, struct user_profile *src) { memcpy(dst, src, sizeof(*dst)); } const struct user_profile *conf_find_user_profile(struct ast_channel *chan, const char *user_profile_name, struct user_profile *result) { struct user_profile tmp; struct user_profile *tmp2; struct ast_datastore *datastore = NULL; struct func_confbridge_data *b_data = NULL; ast_copy_string(tmp.name, user_profile_name, sizeof(tmp.name)); if (chan) { ast_channel_lock(chan); if ((datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL))) { ast_channel_unlock(chan); b_data = datastore->data; if (b_data->u_usable) { conf_user_profile_copy(result, &b_data->u_profile); return result; } } else { ast_channel_unlock(chan); } } if (ast_strlen_zero(user_profile_name)) { user_profile_name = DEFAULT_USER_PROFILE; } if (!(tmp2 = ao2_find(user_profiles, &tmp, OBJ_POINTER))) { return NULL; } ao2_lock(tmp2); conf_user_profile_copy(result, tmp2); ao2_unlock(tmp2); ao2_ref(tmp2, -1); return result; } void conf_bridge_profile_copy(struct bridge_profile *dst, struct bridge_profile *src) { memcpy(dst, src, sizeof(*dst)); if (src->sounds) { ao2_ref(src->sounds, +1); } } void conf_bridge_profile_destroy(struct bridge_profile *b_profile) { if (b_profile->sounds) { ao2_ref(b_profile->sounds, -1); b_profile->sounds = NULL; } } const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, const char *bridge_profile_name, struct bridge_profile *result) { struct bridge_profile tmp; struct bridge_profile *tmp2; struct ast_datastore *datastore = NULL; struct func_confbridge_data *b_data = NULL; if (chan) { ast_channel_lock(chan); if ((datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL))) { ast_channel_unlock(chan); b_data = datastore->data; if (b_data->b_usable) { conf_bridge_profile_copy(result, &b_data->b_profile); return result; } } else { ast_channel_unlock(chan); } } if (ast_strlen_zero(bridge_profile_name)) { bridge_profile_name = DEFAULT_BRIDGE_PROFILE; } ast_copy_string(tmp.name, bridge_profile_name, sizeof(tmp.name)); if (!(tmp2 = ao2_find(bridge_profiles, &tmp, OBJ_POINTER))) { return NULL; } ao2_lock(tmp2); conf_bridge_profile_copy(result, tmp2); ao2_unlock(tmp2); ao2_ref(tmp2, -1); return result; } struct dtmf_menu_hook_pvt { struct conference_bridge_user *conference_bridge_user; struct conf_menu_entry menu_entry; struct conf_menu *menu; }; 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); while ((action = AST_LIST_REMOVE_HEAD(&pvt->menu_entry.actions, action))) { ast_free(action); } ast_free(pvt); } static int menu_hook_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) { struct dtmf_menu_hook_pvt *pvt = hook_pvt; return conf_handle_dtmf(bridge_channel, pvt->conference_bridge_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; while ((menu_action = AST_LIST_REMOVE_HEAD(&menu_entry->actions, action))) { ast_free(menu_action); } } int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu *menu, struct conf_menu_entry *result) { struct conf_menu_entry *menu_entry = NULL; ao2_lock(menu); AST_LIST_TRAVERSE(&menu->entries, menu_entry, entry) { if (!strcasecmp(menu_entry->dtmf, dtmf_sequence)) { copy_menu_entry(result, menu_entry); ao2_unlock(menu); return 1; } } ao2_unlock(menu); return 0; } int conf_set_menu_to_user(const char *menu_name, struct conference_bridge_user *conference_bridge_user) { struct conf_menu tmp; struct conf_menu *menu; struct conf_menu_entry *menu_entry = NULL; ast_copy_string(tmp.name, menu_name, sizeof(tmp.name)); if (!(menu = ao2_find(menus, &tmp, OBJ_POINTER))) { return -1; } ao2_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->conference_bridge_user = conference_bridge_user; ao2_ref(menu, +1); pvt->menu = menu; ast_bridge_features_hook(&conference_bridge_user->features, pvt->menu_entry.dtmf, menu_hook_callback, pvt, menu_hook_destroy); } ao2_unlock(menu); ao2_ref(menu, -1); return 0; }