/* * 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 */ /*** MODULEINFO core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/logger.h" #include "asterisk/config.h" #include "asterisk/config_options.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" struct confbridge_cfg { struct ao2_container *bridge_profiles; struct ao2_container *user_profiles; struct ao2_container *menus; }; static void *bridge_profile_alloc(const char *category); static void *bridge_profile_find(struct ao2_container *container, const char *category); static struct bridge_profile_sounds *bridge_profile_sounds_alloc(void); static void bridge_profile_destructor(void *obj) { struct bridge_profile *b_profile = obj; ao2_cleanup(b_profile->sounds); } static void *bridge_profile_alloc(const char *category) { struct bridge_profile *b_profile; if (!(b_profile = ao2_alloc(sizeof(*b_profile), bridge_profile_destructor))) { return NULL; } if (!(b_profile->sounds = bridge_profile_sounds_alloc())) { ao2_ref(b_profile, -1); return NULL; } ast_copy_string(b_profile->name, category, sizeof(b_profile->name)); return b_profile; } static void *bridge_profile_find(struct ao2_container *container, const char *category) { return ao2_find(container, category, OBJ_KEY); } static struct aco_type bridge_type = { .type = ACO_ITEM, .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", .matchvalue = "bridge", .item_alloc = bridge_profile_alloc, .item_find = bridge_profile_find, .item_offset = offsetof(struct confbridge_cfg, bridge_profiles), }; static void *user_profile_alloc(const char *category); static void *user_profile_find(struct ao2_container *container, const char *category); static void user_profile_destructor(void *obj) { return; } static void *user_profile_alloc(const char *category) { struct user_profile *u_profile; if (!(u_profile = ao2_alloc(sizeof(*u_profile), user_profile_destructor))) { return NULL; } ast_copy_string(u_profile->name, category, sizeof(u_profile->name)); return u_profile; } static void *user_profile_find(struct ao2_container *container, const char *category) { return ao2_find(container, category, OBJ_KEY); } static struct aco_type user_type = { .type = ACO_ITEM, .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", .matchvalue = "user", .item_alloc = user_profile_alloc, .item_find = user_profile_find, .item_offset = offsetof(struct confbridge_cfg, user_profiles), }; static void *menu_alloc(const char *category); static void *menu_find(struct ao2_container *container, const char *category); static void menu_destructor(void *obj); static void *menu_alloc(const char *category) { struct conf_menu *menu; if (!(menu = ao2_alloc(sizeof(*menu), menu_destructor))) { return NULL; } ast_copy_string(menu->name, category, sizeof(menu->name)); return menu; } static void *menu_find(struct ao2_container *container, const char *category) { return ao2_find(container, category, OBJ_KEY); } static struct aco_type menu_type = { .type = ACO_ITEM, .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", .matchvalue = "menu", .item_alloc = menu_alloc, .item_find = menu_find, .item_offset = offsetof(struct confbridge_cfg, menus), }; /* Used to pass to aco_option_register */ static struct aco_type *bridge_types[] = ACO_TYPES(&bridge_type); static struct aco_type *menu_types[] = ACO_TYPES(&menu_type); static struct aco_type *user_types[] = ACO_TYPES(&user_type); /* The general category is reserved, but unused */ static struct aco_type general_type = { .type = ACO_GLOBAL, .category_match = ACO_WHITELIST, .category = "^general$", }; static struct aco_file confbridge_conf = { .filename = "confbridge.conf", .types = ACO_TYPES(&bridge_type, &user_type, &menu_type, &general_type), }; static AO2_GLOBAL_OBJ_STATIC(cfg_handle); static void *confbridge_cfg_alloc(void); CONFIG_INFO_STANDARD(cfg_info, cfg_handle, confbridge_cfg_alloc, .files = ACO_FILES(&confbridge_conf), ); /*! bridge profile container functions */ static int bridge_cmp_cb(void *obj, void *arg, int flags) { const struct bridge_profile *entry1 = obj, *entry2 = arg; const char *name = arg; return (!strcasecmp(entry1->name, flags & OBJ_KEY ? 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; const char *name = obj; return ast_str_case_hash(flags & OBJ_KEY ? name : b_profile->name); } /*! menu container functions */ static int menu_cmp_cb(void *obj, void *arg, int flags) { const struct conf_menu *entry1 = obj, *entry2 = arg; const char *name = arg; return (!strcasecmp(entry1->name, flags & OBJ_KEY ? 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; const char *name = obj; return ast_str_case_hash(flags & OBJ_KEY ? name : menu->name); } 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, *entry2 = arg; const char *name = arg; return (!strcasecmp(entry1->name, flags & OBJ_KEY ? 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; const char *name = obj; return ast_str_case_hash(flags & OBJ_KEY ? name : u_profile->name); } /*! 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_sound(const char *sound_name, const char *sound_file, struct bridge_profile *b_profile) { struct bridge_profile_sounds *sounds = b_profile->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_leader_has_left")) { ast_string_field_set(sounds, leaderhasleft, 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; } /*! 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; struct ast_variable tmpvar = { 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; } tmpvar.name = args.option; tmpvar.value = value; tmpvar.file = "CONFBRIDGE"; /* SET(CONFBRIDGE(type,option)=value) */ if (!strcasecmp(args.type, "bridge") && !aco_process_var(&bridge_type, "dialplan", &tmpvar, &b_data->b_profile)) { b_data->b_usable = 1; } else if (!strcasecmp(args.type, "user") && !aco_process_var(&user_type, "dialplan", &tmpvar, &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; } 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 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; RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); if (!cfg) { return NULL; } i = ao2_iterator_init(cfg->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; RAII_VAR(struct confbridge_cfg *, cfg, NULL, ao2_cleanup); 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; } if (!(cfg = ao2_global_obj_ref(cfg_handle))) { return NULL; } ast_cli(a->fd,"--------- User Profiles -----------\n"); ao2_lock(cfg->user_profiles); it = ao2_iterator_init(cfg->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(cfg->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; RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); if (!cfg) { return NULL; } i = ao2_iterator_init(cfg->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; RAII_VAR(struct confbridge_cfg *, cfg, NULL, ao2_cleanup); 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; } if (!(cfg = ao2_global_obj_ref(cfg_handle))) { return NULL; } ast_cli(a->fd,"--------- Bridge Profiles -----------\n"); ao2_lock(cfg->bridge_profiles); it = ao2_iterator_init(cfg->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(cfg->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_leader_has_left: %s\n", conf_get_sound(CONF_SOUND_LEADER_HAS_LEFT, 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; RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); if (!cfg) { return NULL; } i = ao2_iterator_init(cfg->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; RAII_VAR(struct confbridge_cfg *, cfg, NULL, ao2_cleanup); 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; } if (!(cfg = ao2_global_obj_ref(cfg_handle))) { return NULL; } ast_cli(a->fd,"--------- Menus -----------\n"); ao2_lock(cfg->menus); it = ao2_iterator_init(cfg->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(cfg->menus); return CLI_SUCCESS; } static char *handle_cli_confbridge_show_menu(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { RAII_VAR(struct conf_menu *, menu, NULL, ao2_cleanup); RAII_VAR(struct confbridge_cfg *, cfg, NULL, ao2_cleanup); 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; } if (!(cfg = ao2_global_obj_ref(cfg_handle))) { return NULL; } if (!(menu = menu_find(cfg->menus, a->argv[3]))) { 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); 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 void confbridge_cfg_destructor(void *obj) { struct confbridge_cfg *cfg = obj; ao2_cleanup(cfg->user_profiles); ao2_cleanup(cfg->bridge_profiles); ao2_cleanup(cfg->menus); } void *confbridge_cfg_alloc(void) { struct confbridge_cfg *cfg; if (!(cfg = ao2_alloc(sizeof(*cfg), confbridge_cfg_destructor))) { return NULL; } if (!(cfg->user_profiles = ao2_container_alloc(283, user_hash_cb, user_cmp_cb))) { goto error; } if (!(cfg->bridge_profiles = ao2_container_alloc(283, bridge_hash_cb, bridge_cmp_cb))) { goto error; } if (!(cfg->menus = ao2_container_alloc(283, menu_hash_cb, menu_cmp_cb))) { goto error; } return cfg; error: ao2_ref(cfg, -1); return NULL; } static int announce_user_count_all_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct user_profile *u_profile = obj; if (strcasecmp(var->name, "announce_user_count_all")) { return -1; } if (ast_true(var->value)) { u_profile->flags = u_profile->flags | USER_OPT_ANNOUNCEUSERCOUNTALL; } else if (ast_false(var->value)) { u_profile->flags = u_profile->flags & ~USER_OPT_ANNOUNCEUSERCOUNTALL; } else if (sscanf(var->value, "%30u", &u_profile->announce_user_count_all_after) == 1) { u_profile->flags = u_profile->flags | USER_OPT_ANNOUNCEUSERCOUNTALL; } else { return -1; } return 0; } static int mix_interval_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct bridge_profile *b_profile = obj; if (strcasecmp(var->name, "mixing_interval")) { return -1; } if (sscanf(var->value, "%30u", &b_profile->mix_interval) != 1) { return -1; } switch (b_profile->mix_interval) { case 10: case 20: case 40: case 80: return 0; default: return -1; } } static int video_mode_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct bridge_profile *b_profile = obj; if (strcasecmp(var->name, "video_mode")) { return -1; } if (!strcasecmp(var->value, "first_marked")) { ast_set_flag(b_profile, BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED); } else if (!strcasecmp(var->value, "last_marked")) { ast_set_flag(b_profile, BRIDGE_OPT_VIDEO_SRC_LAST_MARKED); } else if (!strcasecmp(var->value, "follow_talker")) { ast_set_flag(b_profile, BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER); } else if (!strcasecmp(var->value, "none")) { return 0; } else { return -1; } return 0; } static int user_template_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct user_profile *u_profile = obj; return conf_find_user_profile(NULL, var->value, u_profile) ? 0 : -1; } static int bridge_template_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct bridge_profile *b_profile = obj; 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, var->value, b_profile))) { 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, b_profile->sounds->onlyperson); ast_string_field_set(sounds, hasjoin, b_profile->sounds->hasjoin); ast_string_field_set(sounds, hasleft, b_profile->sounds->hasleft); ast_string_field_set(sounds, kicked, b_profile->sounds->kicked); ast_string_field_set(sounds, muted, b_profile->sounds->muted); ast_string_field_set(sounds, unmuted, b_profile->sounds->unmuted); ast_string_field_set(sounds, thereare, b_profile->sounds->thereare); ast_string_field_set(sounds, otherinparty, b_profile->sounds->otherinparty); ast_string_field_set(sounds, placeintoconf, b_profile->sounds->placeintoconf); ast_string_field_set(sounds, waitforleader, b_profile->sounds->waitforleader); ast_string_field_set(sounds, leaderhasleft, b_profile->sounds->leaderhasleft); ast_string_field_set(sounds, getpin, b_profile->sounds->getpin); ast_string_field_set(sounds, invalidpin, b_profile->sounds->invalidpin); ast_string_field_set(sounds, locked, b_profile->sounds->locked); ast_string_field_set(sounds, unlockednow, b_profile->sounds->unlockednow); ast_string_field_set(sounds, lockednow, b_profile->sounds->lockednow); ast_string_field_set(sounds, errormenu, b_profile->sounds->errormenu); ast_string_field_set(sounds, participantsmuted, b_profile->sounds->participantsmuted); ast_string_field_set(sounds, participantsunmuted, b_profile->sounds->participantsunmuted); ao2_ref(b_profile->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 */ b_profile->sounds = sounds; /* the new sounds struct that is a deep copy of the one from the template. */ 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); return 0; } static int menu_option_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { add_menu_entry(obj, var->name, var->value); return 0; } int conf_load_config(int reload) { if (!reload) { if (aco_info_init(&cfg_info)) { return -1; } } /* User options */ aco_option_register(&cfg_info, "type", ACO_EXACT, user_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "type", ACO_EXACT, bridge_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "type", ACO_EXACT, menu_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "admin", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ADMIN); aco_option_register(&cfg_info, "marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_MARKEDUSER); aco_option_register(&cfg_info, "startmuted", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_STARTMUTED); aco_option_register(&cfg_info, "music_on_hold_when_empty", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_MUSICONHOLD); aco_option_register(&cfg_info, "quiet", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_QUIET); aco_option_register_custom(&cfg_info, "announce_user_count_all", ACO_EXACT, user_types, "no", announce_user_count_all_handler, 0); aco_option_register(&cfg_info, "announce_user_count", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ANNOUNCEUSERCOUNT); /* Negative logic. Defaults to "yes" and evaluates with ast_false(). If !ast_false(), USER_OPT_NOONLYPERSON is cleared */ aco_option_register(&cfg_info, "announce_only_user", ACO_EXACT, user_types, "yes", OPT_BOOLFLAG_T, 0, FLDSET(struct user_profile, flags), USER_OPT_NOONLYPERSON); aco_option_register(&cfg_info, "wait_marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_WAITMARKED); aco_option_register(&cfg_info, "end_marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ENDMARKED); aco_option_register(&cfg_info, "talk_detection_events", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_TALKER_DETECT); aco_option_register(&cfg_info, "dtmf_passthrough", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_DTMF_PASS); aco_option_register(&cfg_info, "announce_join_leave", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ANNOUNCE_JOIN_LEAVE); aco_option_register(&cfg_info, "pin", ACO_EXACT, user_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct user_profile, pin)); aco_option_register(&cfg_info, "music_on_hold_class", ACO_EXACT, user_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct user_profile, moh_class)); aco_option_register(&cfg_info, "announcement", ACO_EXACT, user_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct user_profile, announcement)); aco_option_register(&cfg_info, "denoise", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_DENOISE); aco_option_register(&cfg_info, "dsp_drop_silence", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_DROP_SILENCE); aco_option_register(&cfg_info, "dsp_silence_threshold", ACO_EXACT, user_types, __stringify(DEFAULT_SILENCE_THRESHOLD), OPT_UINT_T, 0, FLDSET(struct user_profile, silence_threshold)); aco_option_register(&cfg_info, "dsp_talking_threshold", ACO_EXACT, user_types, __stringify(DEFAULT_TALKING_THRESHOLD), OPT_UINT_T, 0, FLDSET(struct user_profile, silence_threshold)); aco_option_register(&cfg_info, "jitterbuffer", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_JITTERBUFFER); /* This option should only be used with the CONFBRIDGE dialplan function */ aco_option_register_custom(&cfg_info, "template", ACO_EXACT, user_types, NULL, user_template_handler, 0); /* Bridge options */ aco_option_register(&cfg_info, "jitterbuffer", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), USER_OPT_JITTERBUFFER); /* "auto" will fail to parse as a uint, but we use PARSE_DEFAULT to set the value to 0 in that case, which is the value that auto resolves to */ aco_option_register(&cfg_info, "internal_sample_rate", ACO_EXACT, bridge_types, "0", OPT_UINT_T, PARSE_DEFAULT, FLDSET(struct bridge_profile, internal_sample_rate), 0); aco_option_register_custom(&cfg_info, "mixing_interval", ACO_EXACT, bridge_types, "20", mix_interval_handler, 0); aco_option_register(&cfg_info, "record_conference", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), BRIDGE_OPT_RECORD_CONFERENCE); aco_option_register_custom(&cfg_info, "video_mode", ACO_EXACT, bridge_types, NULL, video_mode_handler, 0); aco_option_register(&cfg_info, "max_members", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, max_members)); aco_option_register(&cfg_info, "record_file", ACO_EXACT, bridge_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, rec_file)); aco_option_register_custom(&cfg_info, "^sound_", ACO_REGEX, bridge_types, NULL, sound_option_handler, 0); /* This option should only be used with the CONFBRIDGE dialplan function */ aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0); /* Menu options */ aco_option_register_custom(&cfg_info, "^[0-9A-D*#]+$", ACO_REGEX, menu_types, NULL, menu_option_handler, 0); if (aco_process_config(&cfg_info, reload) == ACO_PROCESS_ERROR) { goto error; } if (!reload && ast_cli_register_multiple(cli_confbridge_parser, ARRAY_LEN(cli_confbridge_parser))) { goto error; } return 0; error: /* On a reload, just keep the config we already have in place. */ if (!reload) { conf_destroy_config(); } return -1; } 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 *tmp2; struct ast_datastore *datastore = NULL; 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) { 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(cfg->user_profiles, user_profile_name, OBJ_KEY))) { 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 *tmp2; struct ast_datastore *datastore = NULL; 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) { 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; } if (!(tmp2 = ao2_find(cfg->bridge_profiles, bridge_profile_name, OBJ_KEY))) { 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 *menu; struct conf_menu_entry *menu_entry = NULL; RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup); if (!cfg) { return -1; } if (!(menu = menu_find(cfg->menus, menu_name))) { 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; } void conf_destroy_config(void) { ast_cli_unregister_multiple(cli_confbridge_parser, ARRAY_LEN(cli_confbridge_parser)); aco_info_destroy(&cfg_info); ao2_global_obj_release(cfg_handle); }