Skip to content
Snippets Groups Projects
app_confbridge.c 132 KiB
Newer Older
  • Learn to ignore specific revisions
  • 		play_file(bridge_channel, NULL,
    
    			conf_get_sound(CONF_SOUND_ERROR_MENU, conference->b_profile.sounds));
    
    	} else if (!last_user->kicked) {
    
    		pbx_builtin_setvar_helper(last_user->chan, "CONFBRIDGE_RESULT", "KICKED");
    
    		ast_bridge_remove(conference->bridge, last_user->chan);
    		ao2_unlock(conference);
    
    	return 0;
    }
    
    static int action_dialplan_exec(struct ast_bridge_channel *bridge_channel, struct conf_menu_action *menu_action)
    {
    	struct ast_pbx_args args;
    	struct ast_pbx *pbx;
    	char *exten;
    	char *context;
    	int priority;
    	int res;
    
    	memset(&args, 0, sizeof(args));
    	args.no_hangup_chan = 1;
    
    	ast_channel_lock(bridge_channel->chan);
    
    	/*save off*/
    
    	exten = ast_strdupa(ast_channel_exten(bridge_channel->chan));
    	context = ast_strdupa(ast_channel_context(bridge_channel->chan));
    
    	priority = ast_channel_priority(bridge_channel->chan);
    	pbx = ast_channel_pbx(bridge_channel->chan);
    	ast_channel_pbx_set(bridge_channel->chan, NULL);
    
    	ast_channel_exten_set(bridge_channel->chan, menu_action->data.dialplan_args.exten);
    	ast_channel_context_set(bridge_channel->chan, menu_action->data.dialplan_args.context);
    
    	ast_channel_priority_set(bridge_channel->chan, menu_action->data.dialplan_args.priority);
    
    
    	ast_channel_unlock(bridge_channel->chan);
    
    	/*execute*/
    	res = ast_pbx_run_args(bridge_channel->chan, &args);
    
    	/*restore*/
    	ast_channel_lock(bridge_channel->chan);
    
    
    	ast_channel_exten_set(bridge_channel->chan, exten);
    	ast_channel_context_set(bridge_channel->chan, context);
    
    	ast_channel_priority_set(bridge_channel->chan, priority);
    	ast_channel_pbx_set(bridge_channel->chan, pbx);
    
    
    	ast_channel_unlock(bridge_channel->chan);
    
    	return res;
    }
    
    
    static int execute_menu_entry(struct confbridge_conference *conference,
    	struct confbridge_user *user,
    
    	struct ast_bridge_channel *bridge_channel,
    	struct conf_menu_entry *menu_entry,
    	struct conf_menu *menu)
    {
    	struct conf_menu_action *menu_action;
    
    	int isadmin = ast_test_flag(&user->u_profile, USER_OPT_ADMIN);
    
    	int stop_prompts = 0;
    	int res = 0;
    
    	AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) {
    		switch (menu_action->id) {
    		case MENU_ACTION_TOGGLE_MUTE:
    
    			res |= action_toggle_mute(conference, user, bridge_channel);
    
    		case MENU_ACTION_TOGGLE_BINAURAL:
    			action_toggle_binaural(conference, user, bridge_channel);
    			break;
    
    		case MENU_ACTION_ADMIN_TOGGLE_MUTE_PARTICIPANTS:
    			if (!isadmin) {
    				break;
    			}
    
    			action_toggle_mute_participants(conference, user);
    
    			announce_user_count(conference, user, bridge_channel);
    
    		case MENU_ACTION_PLAYBACK:
    			if (!stop_prompts) {
    				res |= action_playback(bridge_channel, menu_action->data.playback_file);
    
    				ast_test_suite_event_notify("CONF_MENU_PLAYBACK",
    					"Message: %s\r\nChannel: %s",
    					menu_action->data.playback_file, ast_channel_name(bridge_channel->chan));
    
    			}
    			break;
    		case MENU_ACTION_RESET_LISTENING:
    
    			ast_audiohook_volume_set(user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, 0);
    
    			break;
    		case MENU_ACTION_RESET_TALKING:
    
    			ast_audiohook_volume_set(user->chan, AST_AUDIOHOOK_DIRECTION_READ, 0);
    
    			break;
    		case MENU_ACTION_INCREASE_LISTENING:
    
    			ast_audiohook_volume_adjust(user->chan,
    
    				AST_AUDIOHOOK_DIRECTION_WRITE, 1);
    			break;
    		case MENU_ACTION_DECREASE_LISTENING:
    
    			ast_audiohook_volume_adjust(user->chan,
    
    				AST_AUDIOHOOK_DIRECTION_WRITE, -1);
    			break;
    		case MENU_ACTION_INCREASE_TALKING:
    
    			ast_audiohook_volume_adjust(user->chan,
    
    				AST_AUDIOHOOK_DIRECTION_READ, 1);
    			break;
    		case MENU_ACTION_DECREASE_TALKING:
    
    			ast_audiohook_volume_adjust(user->chan,
    
    				AST_AUDIOHOOK_DIRECTION_READ, -1);
    			break;
    		case MENU_ACTION_PLAYBACK_AND_CONTINUE:
    			if (!(stop_prompts)) {
    
    				res |= action_playback_and_continue(conference,
    					user,
    
    					bridge_channel,
    					menu,
    					menu_action->data.playback_file,
    					menu_entry->dtmf,
    					&stop_prompts);
    			}
    			break;
    		case MENU_ACTION_DIALPLAN_EXEC:
    			res |= action_dialplan_exec(bridge_channel, menu_action);
    			break;
    		case MENU_ACTION_ADMIN_TOGGLE_LOCK:
    			if (!isadmin) {
    				break;
    			}
    
    			conference->locked = (!conference->locked ? 1 : 0);
    
    			res |= play_file(bridge_channel, NULL,
    
    				conf_get_sound(
    					conference->locked ? CONF_SOUND_LOCKED_NOW : CONF_SOUND_UNLOCKED_NOW,
    					conference->b_profile.sounds)) < 0;
    
    			break;
    		case MENU_ACTION_ADMIN_KICK_LAST:
    
    			res |= action_kick_last(conference, bridge_channel, user);
    
    			break;
    		case MENU_ACTION_LEAVE:
    
    			pbx_builtin_setvar_helper(bridge_channel->chan, "CONFBRIDGE_RESULT", "DTMF");
    
    			ao2_lock(conference);
    			ast_bridge_remove(conference->bridge, bridge_channel->chan);
    
    			ast_test_suite_event_notify("CONF_MENU_LEAVE",
    				"Channel: %s",
    				ast_channel_name(bridge_channel->chan));
    
    			break;
    		case MENU_ACTION_NOOP:
    			break;
    
    		case MENU_ACTION_SET_SINGLE_VIDEO_SRC:
    
    			if (!ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) {
    				ast_bridge_set_single_src_video_mode(conference->bridge, bridge_channel->chan);
    			}
    
    			break;
    
    			handle_video_on_exit(conference, bridge_channel->chan);
    
    int conf_handle_dtmf(struct ast_bridge_channel *bridge_channel,
    
    	struct confbridge_user *user,
    
    	struct conf_menu_entry *menu_entry,
    	struct conf_menu *menu)
    {
    	/* See if music on hold is playing */
    
    
    	/* execute the list of actions associated with this menu entry */
    
    	execute_menu_entry(user->conference, user, bridge_channel, menu_entry, menu);
    
    
    	/* See if music on hold needs to be started back up again */
    
    	conf_moh_unsuspend(user);
    
    	async_play_sound_ready(bridge_channel->chan);
    
    
    static int kick_conference_participant(struct confbridge_conference *conference,
    	const char *channel)
    
    	struct confbridge_user *user = NULL;
    
    	int all = !strcasecmp("all", channel);
    	int participants = !strcasecmp("participants", channel);
    
    	SCOPED_AO2LOCK(bridge_lock, conference);
    
    	AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    
    		if (user->kicked) {
    			continue;
    		}
    		match = !strcasecmp(channel, ast_channel_name(user->chan));
    		if (match || all
    				|| (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
    
    			pbx_builtin_setvar_helper(user->chan, "CONFBRIDGE_RESULT", "KICKED");
    
    			ast_bridge_remove(conference->bridge, user->chan);
    			res = 0;
    
    	AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
    
    		if (user->kicked) {
    			continue;
    		}
    		match = !strcasecmp(channel, ast_channel_name(user->chan));
    		if (match || all
    				|| (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
    
    			pbx_builtin_setvar_helper(user->chan, "CONFBRIDGE_RESULT", "KICKED");
    
    			ast_bridge_remove(conference->bridge, user->chan);
    			res = 0;
    
    static char *complete_confbridge_name(const char *line, const char *word, int pos, int state)
    {
    	int which = 0;
    
    	struct confbridge_conference *conference;
    
    	char *res = NULL;
    	int wordlen = strlen(word);
    
    	struct ao2_iterator iter;
    
    	iter = ao2_iterator_init(conference_bridges, 0);
    
    	while ((conference = ao2_iterator_next(&iter))) {
    		if (!strncasecmp(conference->name, word, wordlen) && ++which > state) {
    			res = ast_strdup(conference->name);
    			ao2_ref(conference, -1);
    
    		ao2_ref(conference, -1);
    
    	ao2_iterator_destroy(&iter);
    
    static char *complete_confbridge_participant(const char *conference_name, const char *line, const char *word, int pos, int state)
    
    	RAII_VAR(struct confbridge_conference *, conference, NULL, ao2_cleanup);
    	struct confbridge_user *user;
    
    	char *res = NULL;
    	int wordlen = strlen(word);
    
    
    	conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
    
    	if (!strncasecmp("all", word, wordlen) && ++which > state) {
    
    		return ast_strdup("all");
    	}
    
    
    	if (!strncasecmp("participants", word, wordlen) && ++which > state) {
    		return ast_strdup("participants");
    	}
    
    		SCOPED_AO2LOCK(bridge_lock, conference);
    		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    			if (!strncasecmp(ast_channel_name(user->chan), word, wordlen) && ++which > state) {
    				res = ast_strdup(ast_channel_name(user->chan));
    
    		AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
    			if (!strncasecmp(ast_channel_name(user->chan), word, wordlen) && ++which > state) {
    				res = ast_strdup(ast_channel_name(user->chan));
    
    static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    
    	struct confbridge_conference *conference;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge kick";
    		e->usage =
    			"Usage: confbridge kick <conference> <channel>\n"
    
    			"       Kicks a channel out of the conference bridge.\n"
    			"             (all to kick everyone, participants to kick non-admins).\n";
    
    		return NULL;
    	case CLI_GENERATE:
    
    		if (a->pos == 2) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    		if (a->pos == 3) {
    
    			return complete_confbridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
    
    	conference = ao2_find(conference_bridges, a->argv[2], OBJ_KEY);
    
    		ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
    		return CLI_SUCCESS;
    	}
    
    	not_found = kick_conference_participant(conference, a->argv[3]);
    	ao2_ref(conference, -1);
    	if (not_found) {
    
    		if (!strcasecmp("all", a->argv[3]) || !strcasecmp("participants", a->argv[3])) {
    			ast_cli(a->fd, "No participants found!\n");
    		} else {
    			ast_cli(a->fd, "No participant named '%s' found!\n", a->argv[3]);
    		}
    
    	ast_cli(a->fd, "Kicked '%s' out of conference '%s'\n", a->argv[3], a->argv[2]);
    
    static void handle_cli_confbridge_list_item(struct ast_cli_args *a, struct confbridge_user *user, int waiting)
    
    	char flag_str[6 + 1];/* Max flags + terminator */
    
    	int pos = 0;
    
    	/* Build flags column string. */
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
    
    	if (ast_test_flag(&user->u_profile, USER_OPT_ENDMARKED)) {
    
    	if (user->muted) {
    		flag_str[pos++] = 'm';
    	}
    
    	if (waiting) {
    		flag_str[pos++] = 'w';
    	}
    	flag_str[pos] = '\0';
    
    
    	ast_cli(a->fd, "%-30s %-6s %-16s %-16s %-16s %s\n",
    		ast_channel_name(user->chan),
    		flag_str,
    		user->u_profile.name,
    
    		user->conference->b_profile.name,
    
    		user->menu_name,
    		S_COR(ast_channel_caller(user->chan)->id.number.valid,
    			ast_channel_caller(user->chan)->id.number.str, "<unknown>"));
    
    static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    
    	struct confbridge_conference *conference;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge list";
    		e->usage =
    			"Usage: confbridge list [<name>]\n"
    
    			"       Lists all currently active conference bridges or a specific conference bridge.\n"
    			"\n"
    			"       When a conference bridge name is provided, flags may be shown for users. Below\n"
    
    			"       are the flags and what they represent.\n"
    
    			"\n"
    			"       Flags:\n"
    			"         A - The user is an admin\n"
    			"         M - The user is a marked user\n"
    			"         W - The user must wait for a marked user to join\n"
    			"         E - The user will be kicked after the last marked user leaves the conference\n"
    
    			"         w - The user is waiting for a marked user to join\n";
    
    		return NULL;
    	case CLI_GENERATE:
    
    		if (a->pos == 2) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    
    		ast_cli(a->fd, "Conference Bridge Name           Users  Marked Locked Muted\n");
    		ast_cli(a->fd, "================================ ====== ====== ====== =====\n");
    
    		iter = ao2_iterator_init(conference_bridges, 0);
    
    		while ((conference = ao2_iterator_next(&iter))) {
    
    			ast_cli(a->fd, "%-32s %6u %6u %-6s %s\n",
    				conference->name,
    				conference->activeusers + conference->waitingusers,
    				conference->markedusers,
    				AST_CLI_YESNO(conference->locked),
    				AST_CLI_YESNO(conference->muted));
    
    			ao2_ref(conference, -1);
    
    		ao2_iterator_destroy(&iter);
    
    		struct confbridge_user *user;
    
    		conference = ao2_find(conference_bridges, a->argv[2], OBJ_KEY);
    
    			ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
    			return CLI_SUCCESS;
    		}
    
    		ast_cli(a->fd, "Channel                        Flags  User Profile     Bridge Profile   Menu             CallerID\n");
    		ast_cli(a->fd, "============================== ====== ================ ================ ================ ================\n");
    
    		ao2_lock(conference);
    		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    			handle_cli_confbridge_list_item(a, user, 0);
    
    		AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
    			handle_cli_confbridge_list_item(a, user, 1);
    
    		ao2_unlock(conference);
    		ao2_ref(conference, -1);
    
    /* \internal
     * \brief finds a conference by name and locks/unlocks.
     *
     * \retval 0 success
     * \retval -1 conference not found
     */
    
    static int generic_lock_unlock_helper(int lock, const char *conference_name)
    
    	struct confbridge_conference *conference;
    
    	conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
    
    	ao2_lock(conference);
    	conference->locked = lock;
    	ast_test_suite_event_notify("CONF_LOCK", "Message: conference %s\r\nConference: %s", conference->locked ? "locked" : "unlocked", conference->b_profile.name);
    	ao2_unlock(conference);
    	ao2_ref(conference, -1);
    
    
    	return res;
    }
    
    /* \internal
     * \brief finds a conference user by channel name and mutes/unmutes them.
     *
     * \retval 0 success
     * \retval -1 conference not found
     * \retval -2 user not found
     */
    
    static int generic_mute_unmute_helper(int mute, const char *conference_name,
    	const char *chan_name)
    
    	RAII_VAR(struct confbridge_conference *, conference, NULL, ao2_cleanup);
    
    	int all = !strcasecmp("all", chan_name);
    	int participants = !strcasecmp("participants", chan_name);
    	int res = -2;
    
    
    	conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
    
    
    	{
    		SCOPED_AO2LOCK(bridge_lock, conference);
    		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    			int match = !strncasecmp(chan_name, ast_channel_name(user->chan),
    				strlen(chan_name));
    			if (match || all
    				|| (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
    				generic_mute_unmute_user(conference, user, mute);
    				res = 0;
    				if (match) {
    					return res;
    				}
    			}
    
    		AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
    
    			int match = !strncasecmp(chan_name, ast_channel_name(user->chan),
    				strlen(chan_name));
    			if (match || all
    				|| (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
    				generic_mute_unmute_user(conference, user, mute);
    				res = 0;
    				if (match) {
    					return res;
    				}
    
    
    	return res;
    }
    
    static int cli_mute_unmute_helper(int mute, struct ast_cli_args *a)
    {
    	int res = generic_mute_unmute_helper(mute, a->argv[2], a->argv[3]);
    
    	if (res == -1) {
    		ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
    		return -1;
    	} else if (res == -2) {
    
    		if (!strcasecmp("all", a->argv[3]) || !strcasecmp("participants", a->argv[3])) {
    			ast_cli(a->fd, "No participants found in conference %s\n", a->argv[2]);
    		} else {
    			ast_cli(a->fd, "No channel named '%s' found in conference %s\n", a->argv[3], a->argv[2]);
    		}
    
    		return -1;
    	}
    	ast_cli(a->fd, "%s %s from confbridge %s\n", mute ? "Muting" : "Unmuting", a->argv[3], a->argv[2]);
    	return 0;
    }
    
    static char *handle_cli_confbridge_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge mute";
    		e->usage =
    
    			"Usage: confbridge mute <conference> <channel>\n"
    
    			"       Mute a channel in a conference.\n"
    
    			"              (all to mute everyone, participants to mute non-admins)\n"
    
    			"       If the specified channel is a prefix,\n"
    			"       the action will be taken on the first\n"
    			"       matching channel.\n";
    
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    
    		if (a->pos == 3) {
    			return complete_confbridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
    		}
    
    		return NULL;
    	}
    	if (a->argc != 4) {
    		return CLI_SHOWUSAGE;
    	}
    
    	cli_mute_unmute_helper(1, a);
    
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_confbridge_unmute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge unmute";
    		e->usage =
    
    			"Usage: confbridge unmute <conference> <channel>\n"
    
    			"       Unmute a channel in a conference.\n"
    
    			"              (all to unmute everyone, participants to unmute non-admins)\n"
    
    			"       If the specified channel is a prefix,\n"
    			"       the action will be taken on the first\n"
    			"       matching channel.\n";
    
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    
    		if (a->pos == 3) {
    			return complete_confbridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
    		}
    
    		return NULL;
    	}
    	if (a->argc != 4) {
    		return CLI_SHOWUSAGE;
    	}
    
    	cli_mute_unmute_helper(0, a);
    
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_confbridge_lock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge lock";
    		e->usage =
    
    			"Usage: confbridge lock <conference>\n"
    			"       Lock a conference. While locked, no new non-admins\n"
    			"       may join the conference.\n";
    
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    		return NULL;
    	}
    	if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    	if (generic_lock_unlock_helper(1, a->argv[2])) {
    		ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]);
    	} else {
    		ast_cli(a->fd, "Conference %s is locked.\n", a->argv[2]);
    	}
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_confbridge_unlock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge unlock";
    		e->usage =
    
    			"Usage: confbridge unlock <conference>\n"
    			"       Unlock a previously locked conference.\n";
    
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    		return NULL;
    	}
    	if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    	if (generic_lock_unlock_helper(0, a->argv[2])) {
    		ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]);
    	} else {
    		ast_cli(a->fd, "Conference %s is unlocked.\n", a->argv[2]);
    	}
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_confbridge_start_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	const char *rec_file = NULL;
    
    	struct confbridge_conference *conference;
    
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge record start";
    		e->usage =
    			"Usage: confbridge record start <conference> <file>\n"
    			"       <file> is optional, Otherwise the bridge profile\n"
    			"       record file will be used.  If the bridge profile\n"
    			"       has no record file specified, a file will automatically\n"
    			"       be generated in the monitor directory\n";
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 3) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    		return NULL;
    	}
    	if (a->argc < 4) {
    		return CLI_SHOWUSAGE;
    	}
    	if (a->argc == 5) {
    		rec_file = a->argv[4];
    	}
    
    
    	conference = ao2_find(conference_bridges, a->argv[3], OBJ_KEY);
    
    		ast_cli(a->fd, "Conference not found.\n");
    		return CLI_FAILURE;
    	}
    
    	ao2_lock(conference);
    	if (conf_is_recording(conference)) {
    
    		ast_cli(a->fd, "Conference is already being recorded.\n");
    
    		ao2_unlock(conference);
    		ao2_ref(conference, -1);
    
    		return CLI_SUCCESS;
    	}
    	if (!ast_strlen_zero(rec_file)) {
    
    		ast_copy_string(conference->b_profile.rec_file, rec_file, sizeof(conference->b_profile.rec_file));
    
    		ast_cli(a->fd, "Could not start recording due to internal error.\n");
    
    		ao2_unlock(conference);
    		ao2_ref(conference, -1);
    
    		return CLI_FAILURE;
    	}
    
    	ast_cli(a->fd, "Recording started\n");
    
    	ao2_ref(conference, -1);
    
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_confbridge_stop_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    
    	struct confbridge_conference *conference;
    
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "confbridge record stop";
    		e->usage =
    
    			"Usage: confbridge record stop <conference>\n"
    			"       Stop a previously started recording.\n";
    
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 3) {
    			return complete_confbridge_name(a->line, a->word, a->pos, a->n);
    		}
    		return NULL;
    	}
    	if (a->argc != 4) {
    		return CLI_SHOWUSAGE;
    	}
    
    
    	conference = ao2_find(conference_bridges, a->argv[3], OBJ_KEY);
    
    		ast_cli(a->fd, "Conference not found.\n");
    		return CLI_SUCCESS;
    	}
    
    	ao2_lock(conference);
    	ret = conf_stop_record(conference);
    	ao2_unlock(conference);
    
    	ast_cli(a->fd, "Recording %sstopped.\n", ret ? "could not be " : "");
    
    	ao2_ref(conference, -1);
    
    static struct ast_cli_entry cli_confbridge[] = {
    	AST_CLI_DEFINE(handle_cli_confbridge_list, "List conference bridges and participants."),
    
    	AST_CLI_DEFINE(handle_cli_confbridge_kick, "Kick participants out of conference bridges."),
    
    	AST_CLI_DEFINE(handle_cli_confbridge_mute, "Mute participants."),
    	AST_CLI_DEFINE(handle_cli_confbridge_unmute, "Unmute participants."),
    
    	AST_CLI_DEFINE(handle_cli_confbridge_lock, "Lock a conference."),
    	AST_CLI_DEFINE(handle_cli_confbridge_unlock, "Unlock a conference."),
    	AST_CLI_DEFINE(handle_cli_confbridge_start_record, "Start recording a conference"),
    	AST_CLI_DEFINE(handle_cli_confbridge_stop_record, "Stop recording a conference."),
    };
    static struct ast_custom_function confbridge_function = {
    	.name = "CONFBRIDGE",
    	.write = func_confbridge_helper,
    
    static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
    static struct ast_custom_function confbridge_info_function = {
    	.name = "CONFBRIDGE_INFO",
    	.read = func_confbridge_info,
    };
    
    
    static int action_confbridgelist_item(struct mansession *s, const char *id_text, struct confbridge_conference *conference, struct confbridge_user *user, int waiting)
    
    	struct ast_channel_snapshot *snapshot;
    	struct ast_str *snap_str;
    
    	snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(user->chan));
    	if (!snapshot) {
    		return 0;
    	}
    
    	snap_str = ast_manager_build_channel_state_string(snapshot);
    	if (!snap_str) {
    		ao2_ref(snapshot, -1);
    		return 0;
    	}
    
    
    	astman_append(s,
    		"Event: ConfbridgeList\r\n"
    		"%s"
    		"Conference: %s\r\n"
    		"Admin: %s\r\n"
    		"MarkedUser: %s\r\n"
    
    		"WaitMarked: %s\r\n"
    		"EndMarked: %s\r\n"
    		"Waiting: %s\r\n"
    
    		"AnsweredTime: %d\r\n"
    
    		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_ADMIN)),
    		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)),
    		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)),
    		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_ENDMARKED)),
    		AST_YESNO(waiting),
    		AST_YESNO(user->muted),
    
    		ast_channel_get_up_time(user->chan),
    		ast_str_buffer(snap_str));
    
    	ast_free(snap_str);
    	ao2_ref(snapshot, -1);
    
    	return 1;
    
    static int action_confbridgelist(struct mansession *s, const struct message *m)
    {
    	const char *actionid = astman_get_header(m, "ActionID");
    
    	const char *conference_name = astman_get_header(m, "Conference");
    	struct confbridge_user *user;
    	struct confbridge_conference *conference;
    
    	if (!ast_strlen_zero(actionid)) {
    		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid);
    	}
    
    	if (ast_strlen_zero(conference_name)) {
    
    		astman_send_error(s, m, "No Conference name provided.");
    		return 0;
    	}
    	if (!ao2_container_count(conference_bridges)) {
    		astman_send_error(s, m, "No active conferences.");
    		return 0;
    	}
    
    	conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
    
    		astman_send_error(s, m, "No Conference by that name found.");
    		return 0;
    	}
    
    
    	astman_send_listack(s, m, "Confbridge user list will follow", "start");
    
    	ao2_lock(conference);
    	AST_LIST_TRAVERSE(&conference->active_list, user, list) {
    
    		total += action_confbridgelist_item(s, id_text, conference, user, 0);
    
    	AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
    
    		total += action_confbridgelist_item(s, id_text, conference, user, 1);
    
    	ao2_unlock(conference);
    	ao2_ref(conference, -1);
    
    	astman_send_list_complete_start(s, m, "ConfbridgeListComplete", total);
    	astman_send_list_complete_end(s);
    
    
    	return 0;
    }
    
    static int action_confbridgelistrooms(struct mansession *s, const struct message *m)
    {
    	const char *actionid = astman_get_header(m, "ActionID");
    
    	struct confbridge_conference *conference;
    
    	struct ao2_iterator iter;
    
    	char id_text[512] = "";
    	int totalitems = 0;
    
    	if (!ast_strlen_zero(actionid)) {
    		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid);
    	}
    
    	if (!ao2_container_count(conference_bridges)) {
    		astman_send_error(s, m, "No active conferences.");
    		return 0;
    	}
    
    
    	astman_send_listack(s, m, "Confbridge conferences will follow", "start");
    
    
    	/* Traverse the conference list */
    
    	iter = ao2_iterator_init(conference_bridges, 0);
    
    	while ((conference = ao2_iterator_next(&iter))) {
    
    		astman_append(s,
    		"Event: ConfbridgeListRooms\r\n"
    		"%s"
    		"Conference: %s\r\n"
    
    		"Parties: %u\r\n"
    		"Marked: %u\r\n"
    
    		"Locked: %s\r\n"
    
    		conference->name,
    		conference->activeusers + conference->waitingusers,
    		conference->markedusers,
    
    		AST_YESNO(conference->locked),
    		AST_YESNO(conference->muted));
    
    		ao2_ref(conference, -1);
    
    	ao2_iterator_destroy(&iter);
    
    
    	/* Send final confirmation */
    
    	astman_send_list_complete_start(s, m, "ConfbridgeListRoomsComplete", totalitems);
    	astman_send_list_complete_end(s);
    
    	return 0;
    }
    
    static int action_mute_unmute_helper(struct mansession *s, const struct message *m, int mute)
    {
    
    	const char *conference_name = astman_get_header(m, "Conference");
    	const char *channel_name = astman_get_header(m, "Channel");
    
    	if (ast_strlen_zero(conference_name)) {
    
    		astman_send_error(s, m, "No Conference name provided.");
    		return 0;
    	}
    
    	if (ast_strlen_zero(channel_name)) {
    
    		astman_send_error(s, m, "No channel name provided.");
    		return 0;
    	}
    	if (!ao2_container_count(conference_bridges)) {
    		astman_send_error(s, m, "No active conferences.");
    		return 0;
    	}
    
    
    	res = generic_mute_unmute_helper(mute, conference_name, channel_name);
    
    
    	if (res == -1) {
    		astman_send_error(s, m, "No Conference by that name found.");
    		return 0;
    	} else if (res == -2) {
    		astman_send_error(s, m, "No Channel by that name found in Conference.");
    		return 0;
    	}
    
    	astman_send_ack(s, m, mute ? "User muted" : "User unmuted");
    	return 0;
    }
    
    static int action_confbridgeunmute(struct mansession *s, const struct message *m)
    {
    	return action_mute_unmute_helper(s, m, 0);
    }
    static int action_confbridgemute(struct mansession *s, const struct message *m)
    {
    	return action_mute_unmute_helper(s, m, 1);
    }
    
    static int action_lock_unlock_helper(struct mansession *s, const struct message *m, int lock)
    {
    
    	const char *conference_name = astman_get_header(m, "Conference");
    
    	if (ast_strlen_zero(conference_name)) {
    
    		astman_send_error(s, m, "No Conference name provided.");
    		return 0;
    	}
    	if (!ao2_container_count(conference_bridges)) {
    		astman_send_error(s, m, "No active conferences.");
    		return 0;
    	}
    
    	if ((res = generic_lock_unlock_helper(lock, conference_name))) {
    
    		astman_send_error(s, m, "No Conference by that name found.");
    		return 0;
    	}
    	astman_send_ack(s, m, lock ? "Conference locked" : "Conference unlocked");
    	return 0;
    }
    static int action_confbridgeunlock(struct mansession *s, const struct message *m)
    {
    	return action_lock_unlock_helper(s, m, 0);
    }
    static int action_confbridgelock(struct mansession *s, const struct message *m)
    {
    	return action_lock_unlock_helper(s, m, 1);
    }
    
    static int action_confbridgekick(struct mansession *s, const struct message *m)
    {
    
    	const char *conference_name = astman_get_header(m, "Conference");
    
    	const char *channel = astman_get_header(m, "Channel");
    
    	struct confbridge_conference *conference;
    
    	if (ast_strlen_zero(conference_name)) {
    
    		astman_send_error(s, m, "No Conference name provided.");