Beispiel #1
0
static void call_forwarded_handler(struct stasis_app *app, struct stasis_message *message)
{
	struct ast_multi_channel_blob *payload = stasis_message_data(message);
	struct ast_channel_snapshot *snapshot = ast_multi_channel_blob_get_channel(payload, "forwarded");
	struct ast_channel *chan;

	if (!snapshot) {
		return;
	}

	chan = ast_channel_get_by_name(snapshot->base->uniqueid);
	if (!chan) {
		return;
	}

	app_subscribe_channel(app, chan);
	ast_channel_unref(chan);
}
static int manager_bridge_kick(struct mansession *s, const struct message *m)
{
	const char *bridge_uniqueid = astman_get_header(m, "BridgeUniqueid");
	const char *channel_name = astman_get_header(m, "Channel");
	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel *, channel, NULL, ao2_cleanup);

	if (ast_strlen_zero(channel_name)) {
		astman_send_error(s, m, "Channel must be provided");
		return 0;
	}

	channel = ast_channel_get_by_name(channel_name);
	if (!channel) {
		astman_send_error(s, m, "Channel does not exist");
		return 0;
	}

	if (ast_strlen_zero(bridge_uniqueid)) {
		/* get the bridge from the channel */
		ast_channel_lock(channel);
		bridge = ast_channel_get_bridge(channel);
		ast_channel_unlock(channel);
		if (!bridge) {
			astman_send_error(s, m, "Channel is not in a bridge");
			return 0;
		}
	} else {
		bridge = ast_bridge_find_by_id(bridge_uniqueid);
		if (!bridge) {
			astman_send_error(s, m, "Bridge not found");
			return 0;
		}
	}

	if (ast_bridge_kick(bridge, channel)) {
		astman_send_error(s, m, "Channel kick from bridge failed");
		return 0;
	}

	astman_send_ack(s, m, "Channel has been kicked");
	return 0;
}
Beispiel #3
0
static int manager_park(struct mansession *s, const struct message *m)
{
	const char *channel = astman_get_header(m, "Channel");
	const char *timeout_channel = S_OR(astman_get_header(m, "TimeoutChannel"), astman_get_header(m, "Channel2"));
	const char *announce_channel = astman_get_header(m, "AnnounceChannel");
	const char *timeout = astman_get_header(m, "Timeout");
	const char *parkinglot = astman_get_header(m, "Parkinglot");
	char buf[BUFSIZ];
	int timeout_override = -1;

	RAII_VAR(struct ast_channel *, parker_chan, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);

	if (ast_strlen_zero(channel)) {
		astman_send_error(s, m, "Channel not specified");
		return 0;
	}

	if (!ast_strlen_zero(timeout)) {
		if (sscanf(timeout, "%30d", &timeout_override) != 1 || timeout < 0) {
			astman_send_error(s, m, "Invalid Timeout value.");
			return 0;
		}

		if (timeout_override > 0) {
			/* If greater than zero, convert to seconds for internal use. Must be >= 1 second. */
			timeout_override = MAX(1, timeout_override / 1000);
		}
	}

	if (!(chan = ast_channel_get_by_name(channel))) {
		snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel);
		astman_send_error(s, m, buf);
		return 0;
	}

	ast_channel_lock(chan);
	if (!ast_strlen_zero(timeout_channel)) {
		ast_bridge_set_transfer_variables(chan, timeout_channel, 0);
	}
	ast_channel_unlock(chan);

	parker_chan = ast_channel_bridge_peer(chan);
	if (!parker_chan || strcmp(ast_channel_name(parker_chan), timeout_channel)) {
		if (!ast_strlen_zero(announce_channel)) {
			struct ast_channel *announce_chan = ast_channel_get_by_name(announce_channel);
			if (!announce_channel) {
				astman_send_error(s, m, "AnnounceChannel does not exist");
				return 0;
			}

			create_parked_subscription(announce_chan, ast_channel_uniqueid(chan), 0);
			ast_channel_cleanup(announce_chan);
		}

		manager_park_unbridged(s, m, chan, parkinglot, timeout_override);
		return 0;
	}

	if (!ast_strlen_zero(announce_channel) && strcmp(announce_channel, timeout_channel)) {
		/* When using an announce_channel in bridge mode, only add the announce channel if it isn't
		 * the same as the timeout channel (which will play announcements anyway) */
		struct ast_channel *announce_chan = ast_channel_get_by_name(announce_channel);
		if (!announce_channel) {
			astman_send_error(s, m, "AnnounceChannel does not exist");
			return 0;
		}

		create_parked_subscription(announce_chan, ast_channel_uniqueid(chan), 0);
		ast_channel_cleanup(announce_chan);
	}

	manager_park_bridged(s, m, chan, parker_chan, parkinglot, timeout_override);
	return 0;
}
static int manager_mutestream(struct mansession *s, const struct message *m)
{
	const char *channel = astman_get_header(m, "Channel");
	const char *id = astman_get_header(m,"ActionID");
	const char *state = astman_get_header(m,"State");
	const char *direction = astman_get_header(m,"Direction");
	char id_text[256];
	struct ast_channel *c = NULL;
	struct ast_datastore *datastore = NULL;
	struct mute_information *mute = NULL;
	int is_new = 0;
	int turnon;

	if (ast_strlen_zero(channel)) {
		astman_send_error(s, m, "Channel not specified");
		return 0;
	}
	if (ast_strlen_zero(state)) {
		astman_send_error(s, m, "State not specified");
		return 0;
	}
	if (ast_strlen_zero(direction)) {
		astman_send_error(s, m, "Direction not specified");
		return 0;
	}
	/* Ok, we have everything */

	c = ast_channel_get_by_name(channel);
	if (!c) {
		astman_send_error(s, m, "No such channel");
		return 0;
	}

	ast_channel_lock(c);

	if (!(datastore = ast_channel_datastore_find(c, &mute_datastore, NULL))) {
		if (!(datastore = initialize_mutehook(c))) {
			ast_channel_unlock(c);
			ast_channel_unref(c);
			astman_send_error(s, m, "Memory allocation failure");
			return 0;
		}
		is_new = 1;
	}
	mute = datastore->data;

	turnon = ast_true(state);
	if (!strcasecmp(direction, "in")) {
		mute->mute_read = turnon;
	} else if (!strcasecmp(direction, "out")) {
		mute->mute_write = turnon;
	} else if (!strcasecmp(direction, "all")) {
		mute->mute_read = mute->mute_write = turnon;
	}

	if (is_new) {
		if (mute_add_audiohook(c, mute, datastore)) {
			/* Can't add audiohook */
			ast_datastore_free(datastore);
			ast_free(mute);
			ast_channel_unlock(c);
			ast_channel_unref(c);
			astman_send_error(s, m, "Couldn't add mute audiohook");
			return 0;
		}
	}
	ast_channel_unlock(c);
	ast_channel_unref(c);

	if (!ast_strlen_zero(id)) {
		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
	} else {
		id_text[0] = '\0';
	}
	astman_append(s, "Response: Success\r\n"
		"%s"
		"\r\n", id_text);
	return 0;
}
Beispiel #5
0
static void *channel_find(const struct stasis_app *app, const char *id)
{
	return ast_channel_get_by_name(id);
}
Beispiel #6
0
/*!
 * \internal
 * \brief ast_bridge parking push method.
 * \since 12.0.0
 *
 * \param self Bridge to operate upon
 * \param bridge_channel Bridge channel to push
 * \param swap Bridge channel to swap places with if not NULL
 *
 * \note On entry, self is already locked
 *
 * \retval 0 on success
 * \retval -1 on failure
 */
static int bridge_parking_push(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
{
	struct parked_user *pu;
	const char *blind_transfer;
	RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup); /* XXX replace with ast_channel_cleanup when available */
	RAII_VAR(struct park_common_datastore *, park_datastore, NULL, park_common_datastore_free);

	ast_bridge_base_v_table.push(&self->base, bridge_channel, swap);

	ast_assert(self->lot != NULL);

	/* Answer the channel if needed */
	if (ast_channel_state(bridge_channel->chan) != AST_STATE_UP) {
		ast_answer(bridge_channel->chan);
	}

	if (swap) {
		int use_ringing = 0;

		ast_bridge_channel_lock(swap);
		pu = swap->bridge_pvt;
		if (!pu) {
			/* This should be impossible since the only way a channel can enter in the first place
			 * is if it has a parked user associated with it */
			publish_parked_call_failure(bridge_channel->chan);
			ast_bridge_channel_unlock(swap);
			return -1;
		}

		/* Give the swap channel's parked user reference to the incoming channel */
		pu->chan = bridge_channel->chan;
		bridge_channel->bridge_pvt = pu;
		swap->bridge_pvt = NULL;

		if (ast_bridge_channel_has_role(swap, "holding_participant")) {
			const char *idle_mode = ast_bridge_channel_get_role_option(swap, "holding_participant", "idle_mode");

			if (!ast_strlen_zero(idle_mode) && !strcmp(idle_mode, "ringing")) {
				use_ringing = 1;
			}
		}

		ast_bridge_channel_unlock(swap);

		parking_set_duration(bridge_channel->features, pu);

		if (parking_channel_set_roles(bridge_channel->chan, self->lot, use_ringing)) {
			ast_log(LOG_WARNING, "Failed to apply holding bridge roles to %s while joining the parking lot.\n",
				ast_channel_name(bridge_channel->chan));
		}

		publish_parked_call(pu, PARKED_CALL_SWAP);

		return 0;
	}

	if (!(park_datastore = get_park_common_datastore_copy(bridge_channel->chan))) {
		/* There was either a failure to apply the datastore when performing park common setup or else we had alloc failures while cloning. Abort. */
		return -1;
	}
	parker = ast_channel_get_by_name(park_datastore->parker_uuid);

	/* If the parker and the parkee are the same channel pointer, then the channel entered using
	 * the park application. It's possible that the channel that transferred it is still alive (particularly
	 * when a multichannel bridge is parked), so try to get the real parker if possible. */
	ast_channel_lock(bridge_channel->chan);
	blind_transfer = S_OR(pbx_builtin_getvar_helper(bridge_channel->chan, "BLINDTRANSFER"),
		ast_channel_name(bridge_channel->chan));
	if (blind_transfer) {
		blind_transfer = ast_strdupa(blind_transfer);
	}
	ast_channel_unlock(bridge_channel->chan);

	if (parker == bridge_channel->chan) {
		struct ast_channel *real_parker = ast_channel_get_by_name(blind_transfer);

		if (real_parker) {
			ao2_cleanup(parker);
			parker = real_parker;
		}
	}

	pu = generate_parked_user(self->lot, bridge_channel->chan, parker,
		park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit);
	if (!pu) {
		publish_parked_call_failure(bridge_channel->chan);
		return -1;
	}

	/* If a comeback_override was provided, set it for the parked user's comeback string. */
	if (park_datastore->comeback_override) {
		ast_copy_string(pu->comeback, park_datastore->comeback_override, sizeof(pu->comeback));
	}

	/* Generate ParkedCall Stasis Message */
	publish_parked_call(pu, PARKED_CALL);

	/* If the parkee and the parker are the same and silence_announce isn't set, play the announcement to the parkee */
	if (!strcmp(blind_transfer, ast_channel_name(bridge_channel->chan)) && !park_datastore->silence_announce) {
		char saynum_buf[16];

		snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 0, pu->parking_space);
		ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
	}

	/* Apply parking duration limits */
	parking_set_duration(bridge_channel->features, pu);

	/* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */
	bridge_channel->bridge_pvt = pu;

	ast_verb(3, "Parking '" COLORIZE_FMT "' in '" COLORIZE_FMT "' at space %d\n",
		COLORIZE(COLOR_BRMAGENTA, 0, ast_channel_name(bridge_channel->chan)),
		COLORIZE(COLOR_BRMAGENTA, 0, self->lot->name),
		pu->parking_space);

	parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_INUSE);

	return 0;
}
Beispiel #7
0
static int func_mchan_read(struct ast_channel *chan, const char *function,
                           char *data, struct ast_str **buf, ssize_t len)
{
    struct ast_channel *mchan = ast_channel_get_by_name(chan->linkedid);
    char *template = alloca(4 + strlen(data));
/*!
 * \internal
 * \brief Determine if an extension is a parking extension
 */
static int parking_is_exten_park(const char *context, const char *exten)
{
	struct ast_exten *exten_obj;
	struct pbx_find_info info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
	const char *app_at_exten;

	ast_debug(4, "Checking if %s@%s is a parking exten\n", exten, context);
	exten_obj = pbx_find_extension(NULL, NULL, &info, context, exten, 1, NULL, NULL, E_MATCH);
	if (!exten_obj) {
		return 0;
	}

	app_at_exten = ast_get_extension_app(exten_obj);
	if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) {
		return 0;
	}

	return 1;
}

/*!
 * \internal
 * \since 12.0.0
 * \brief Perform a blind transfer to a parking lot
 *
 * In general, most parking features should work to call this function. This will safely
 * park either a channel in the bridge with \ref bridge_channel or will park the entire
 * bridge if more than one channel is in the bridge. It will create the correct data to
 * pass to the \ref AstBridging Bridging API to safely park the channel.
 *
 * \param bridge_channel The bridge_channel representing the channel performing the park
 * \param context The context to blind transfer to
 * \param exten The extension to blind transfer to
 * \param parked_channel_cb Optional callback executed prior to sending the parked channel into the bridge
 * \param parked_channel_data Data for the parked_channel_cb
 *
 * \retval 0 on success
 * \retval non-zero on error
 */
static int parking_blind_transfer_park(struct ast_bridge_channel *bridge_channel,
		const char *context, const char *exten, transfer_channel_cb parked_channel_cb,
		struct transfer_channel_data *parked_channel_data)
{
	RAII_VAR(struct ast_bridge_channel *, other, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel *, other_chan, NULL, ast_channel_cleanup);

	struct ast_exten *e;
	struct pbx_find_info find_info = { .stacklen = 0 };
	int peer_count;

	if (ast_strlen_zero(context) || ast_strlen_zero(exten)) {
		return -1;
	}

	if (!bridge_channel->in_bridge) {
		return -1;
	}

	if (!parking_is_exten_park(context, exten)) {
		return -1;
	}

	ast_bridge_channel_lock_bridge(bridge_channel);
	peer_count = bridge_channel->bridge->num_channels;
	if (peer_count == 2) {
		other = ast_bridge_channel_peer(bridge_channel);
		ao2_ref(other, +1);
		other_chan = other->chan;
		ast_channel_ref(other_chan);
	}
	ast_bridge_unlock(bridge_channel->bridge);

	if (peer_count < 2) {
		/* There is nothing to do if there is no one to park. */
		return -1;
	}

	/* With a multiparty bridge, we need to do a regular blind transfer. We link the
	 * existing bridge to the parking lot with a Local channel rather than
	 * transferring others. */
	if (peer_count > 2) {
		struct ast_channel *transfer_chan = NULL;

		transfer_chan = park_local_transfer(bridge_channel->chan, context, exten, parked_channel_data);
		if (!transfer_chan) {
			return -1;
		}
		ast_channel_ref(transfer_chan);

		if (parked_channel_cb) {
			parked_channel_cb(transfer_chan, parked_channel_data, AST_BRIDGE_TRANSFER_MULTI_PARTY);
		}

		if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL,
			AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
			ast_hangup(transfer_chan);
			ast_channel_unref(transfer_chan);
			return -1;
		}

		ast_channel_unref(transfer_chan);

		return 0;
	}

	/* Subscribe to park messages with the other channel entering */
	if (create_parked_subscription_full(bridge_channel->chan, ast_channel_uniqueid(other->chan), 1, parked_channel_data)) {
		return -1;
	}

	if (parked_channel_cb) {
		parked_channel_cb(other_chan, parked_channel_data, AST_BRIDGE_TRANSFER_SINGLE_PARTY);
	}

	e = pbx_find_extension(NULL, NULL, &find_info, context, exten, 1, NULL, NULL, E_MATCH);

	/* Write the park frame with the intended recipient and other data out to the bridge. */
	ast_bridge_channel_write_park(bridge_channel,
		ast_channel_uniqueid(other_chan),
		ast_channel_uniqueid(bridge_channel->chan),
		e ? ast_get_extension_app_data(e) : NULL);

	return 0;
}

/*!
 * \internal
 * \since 12.0.0
 * \brief Perform a direct park on a channel in a bridge
 *
 * \note This will be called from within the \ref AstBridging Bridging API
 *
 * \param bridge_channel The bridge_channel representing the channel to be parked
 * \param uuid_parkee The UUID of the channel being parked
 * \param uuid_parker The UUID of the channel performing the park
 * \param app_data Application parseable data to pass to the parking application
 */
static int parking_park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data)
{
	RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
	RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);

	if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) {
		/* We aren't the parkee, so ignore this action. */
		return -1;
	}

	parker = ast_channel_get_by_name(uuid_parker);

	if (!parker) {
		ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker);
		publish_parked_call_failure(bridge_channel->chan);
		return -1;
	}

	if (!(parking_bridge = park_application_setup(bridge_channel->chan, parker, app_data, NULL))) {
		publish_parked_call_failure(bridge_channel->chan);
		return -1;
	}

	ast_bridge_set_transfer_variables(bridge_channel->chan, ast_channel_name(parker), 0);

	/* bridge_channel must be locked so we can get a reference to the bridge it is currently on */
	ao2_lock(bridge_channel);

	original_bridge = bridge_channel->bridge;
	if (!original_bridge) {
		ao2_unlock(bridge_channel);
		publish_parked_call_failure(bridge_channel->chan);
		return -1;
	}

	ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */

	ao2_unlock(bridge_channel);

	if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) {
		ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n",
			ast_channel_name(bridge_channel->chan));
		return -1;
	}

	return 0;
}
Beispiel #9
0
static int cli_channelstats_print_body(void *obj, void *arg, int flags)
{
	struct ast_sip_cli_context *context = arg;
	const struct ast_channel_snapshot *snapshot = obj;
	struct ast_channel *channel = ast_channel_get_by_name(snapshot->name);
	struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL;
	struct ast_sip_session *session;
	struct ast_sip_session_media *media;
	struct ast_rtp_instance *rtp;
	struct ast_rtp_instance_stats stats;
	char *print_name = NULL;
	char *print_time = alloca(32);
	char codec_in_use[7];

	ast_assert(context->output_buffer != NULL);

	if (!channel) {
		ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
		return -1;
	}

	ast_channel_lock(channel);

	session = cpvt->session;
	if (!session) {
		ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
		ast_channel_unlock(channel);
		ao2_cleanup(channel);
		return -1;
	}

	media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
	if (!media || !media->rtp) {
		ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
		ast_channel_unlock(channel);
		ao2_cleanup(channel);
		return -1;
	}

	rtp = ao2_bump(media->rtp);

	codec_in_use[0] = '\0';

	if (ast_channel_rawreadformat(channel)) {
		ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));
	}

	ast_channel_unlock(channel);

	print_name = ast_strdupa(snapshot->name);
	/* Skip the PJSIP/.  We know what channel type it is and we need the space. */
	print_name += 6;

	ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);

	if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
		ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name);
	} else {
		ast_str_append(&context->output_buffer, 0,
			" %8.8s %-18.18s %-8.8s %-6.6s %6u%s %6u%s %3u %7.3f %6u%s %6u%s %3u %7.3f %7.3f\n",
			snapshot->bridgeid,
			print_name,
			print_time,
			codec_in_use,
			stats.rxcount > 100000 ? stats.rxcount / 1000 : stats.rxcount,
			stats.rxcount > 100000 ? "K": " ",
			stats.rxploss > 100000 ? stats.rxploss / 1000 : stats.rxploss,
			stats.rxploss > 100000 ? "K": " ",
			stats.rxcount ? (stats.rxploss * 100) / stats.rxcount : 0,
			MIN(stats.rxjitter, 999.999),
			stats.txcount > 100000 ? stats.txcount / 1000 : stats.txcount,
			stats.txcount > 100000 ? "K": " ",
			stats.txploss > 100000 ? stats.txploss / 1000 : stats.txploss,
			stats.txploss > 100000 ? "K": " ",
			stats.txcount ? (stats.txploss * 100) / stats.txcount : 0,
			MIN(stats.txjitter, 999.999),
			MIN(stats.normdevrtt, 999.999)
		);
	}

	ao2_cleanup(rtp);
	ao2_cleanup(channel);

	return 0;
}
Beispiel #10
0
static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
	int volfactor, const int fd, struct spy_dtmf_options *user_options,
	const char *mygroup, const char *myenforced, const char *spec, const char *exten,
	const char *context, const char *mailbox, const char *name_context)
{
	char nameprefix[AST_NAME_STRLEN];
	char exitcontext[AST_MAX_CONTEXT] = "";
	signed char zero_volume = 0;
	int waitms;
	int res;
	int num_spyed_upon = 1;
	struct ast_channel_iterator *iter = NULL;

	if (ast_test_flag(flags, OPTION_EXIT)) {
		const char *c;
		ast_channel_lock(chan);
		if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT"))) {
			ast_copy_string(exitcontext, c, sizeof(exitcontext));
		} else if (!ast_strlen_zero(ast_channel_macrocontext(chan))) {
			ast_copy_string(exitcontext, ast_channel_macrocontext(chan), sizeof(exitcontext));
		} else {
			ast_copy_string(exitcontext, ast_channel_context(chan), sizeof(exitcontext));
		}
		ast_channel_unlock(chan);
	}

	if (ast_channel_state(chan) != AST_STATE_UP)
		ast_answer(chan);

	ast_set_flag(ast_channel_flags(chan), AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */

	waitms = 100;

	for (;;) {
		struct ast_autochan *autochan = NULL, *next_autochan = NULL;
		struct ast_channel *prev = NULL;

		if (!ast_test_flag(flags, OPTION_QUIET) && num_spyed_upon) {
			res = ast_streamfile(chan, "beep", ast_channel_language(chan));
			if (!res)
				res = ast_waitstream(chan, "");
			else if (res < 0) {
				ast_clear_flag(ast_channel_flags(chan), AST_FLAG_SPYING);
				break;
			}
			if (!ast_strlen_zero(exitcontext)) {
				char tmp[2];
				tmp[0] = res;
				tmp[1] = '\0';
				if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
					goto exit;
				else
					ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
			}
		}

		/* Set up the iterator we'll be using during this call */
		if (!ast_strlen_zero(spec)) {
			if (ast_test_flag(flags, OPTION_UNIQUEID)) {
				struct ast_channel *unique_chan;

				unique_chan = ast_channel_get_by_name(spec);
				if (!unique_chan) {
					res = -1;
					goto exit;
				}
				iter = ast_channel_iterator_by_name_new(ast_channel_name(unique_chan), 0);
				ast_channel_unref(unique_chan);
			} else {
				iter = ast_channel_iterator_by_name_new(spec, strlen(spec));
			}
		} else if (!ast_strlen_zero(exten)) {
			iter = ast_channel_iterator_by_exten_new(exten, context);
		} else {
			iter = ast_channel_iterator_all_new();
		}

		if (!iter) {
			res = -1;
			goto exit;
		}

		res = ast_waitfordigit(chan, waitms);
		if (res < 0) {
			iter = ast_channel_iterator_destroy(iter);
			ast_clear_flag(ast_channel_flags(chan), AST_FLAG_SPYING);
			break;
		}
		if (!ast_strlen_zero(exitcontext)) {
			char tmp[2];
			tmp[0] = res;
			tmp[1] = '\0';
			if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
				iter = ast_channel_iterator_destroy(iter);
				goto exit;
			} else {
				ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
			}
		}

		/* reset for the next loop around, unless overridden later */
		waitms = 100;
		num_spyed_upon = 0;

		for (autochan = next_channel(iter, autochan, chan);
		     autochan;
			 prev = autochan->chan, ast_autochan_destroy(autochan),
		     autochan = next_autochan ? next_autochan : 
				next_channel(iter, autochan, chan), next_autochan = NULL) {
			int igrp = !mygroup;
			int ienf = !myenforced;

			if (autochan->chan == prev) {
				ast_autochan_destroy(autochan);
				break;
			}

			if (ast_check_hangup(chan)) {
				ast_autochan_destroy(autochan);
				break;
			}

			if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_channel_is_bridged(autochan->chan)) {
				continue;
			}

			if (ast_check_hangup(autochan->chan) || ast_test_flag(ast_channel_flags(autochan->chan), AST_FLAG_SPYING)) {
				continue;
			}

			if (mygroup) {
				int num_groups = 0;
				int num_mygroups = 0;
				char dup_group[512];
				char dup_mygroup[512];
				char *groups[NUM_SPYGROUPS];
				char *mygroups[NUM_SPYGROUPS];
				const char *group = NULL;
				int x;
				int y;
				ast_copy_string(dup_mygroup, mygroup, sizeof(dup_mygroup));
				num_mygroups = ast_app_separate_args(dup_mygroup, ':', mygroups,
					ARRAY_LEN(mygroups));

				/* Before dahdi scan was part of chanspy, it would use the "GROUP" variable 
				 * rather than "SPYGROUP", this check is done to preserve expected behavior */
				if (ast_test_flag(flags, OPTION_DAHDI_SCAN)) {
					group = pbx_builtin_getvar_helper(autochan->chan, "GROUP");
				} else {
					group = pbx_builtin_getvar_helper(autochan->chan, "SPYGROUP");
				}

				if (!ast_strlen_zero(group)) {
					ast_copy_string(dup_group, group, sizeof(dup_group));
					num_groups = ast_app_separate_args(dup_group, ':', groups,
						ARRAY_LEN(groups));
				}

				for (y = 0; y < num_mygroups; y++) {
					for (x = 0; x < num_groups; x++) {
						if (!strcmp(mygroups[y], groups[x])) {
							igrp = 1;
							break;
						}
					}
				}
			}

			if (!igrp) {
				continue;
			}
			if (myenforced) {
				char ext[AST_CHANNEL_NAME + 3];
				char buffer[512];
				char *end;

				snprintf(buffer, sizeof(buffer) - 1, ":%s:", myenforced);

				ast_copy_string(ext + 1, ast_channel_name(autochan->chan), sizeof(ext) - 1);
				if ((end = strchr(ext, '-'))) {
					*end++ = ':';
					*end = '\0';
				}

				ext[0] = ':';

				if (strcasestr(buffer, ext)) {
					ienf = 1;
				}
			}

			if (!ienf) {
				continue;
			}

			if (!ast_test_flag(flags, OPTION_QUIET)) {
				char peer_name[AST_NAME_STRLEN + 5];
				char *ptr, *s;

				strcpy(peer_name, "spy-");
				strncat(peer_name, ast_channel_name(autochan->chan), AST_NAME_STRLEN - 4 - 1);
				if ((ptr = strchr(peer_name, '/'))) {
					*ptr++ = '\0';
					for (s = peer_name; s < ptr; s++) {
						*s = tolower(*s);
					}
					if ((s = strchr(ptr, '-'))) {
						*s = '\0';
					}
				}

				if (ast_test_flag(flags, OPTION_NAME)) {
					const char *local_context = S_OR(name_context, "default");
					const char *local_mailbox = S_OR(mailbox, ptr);

					if (local_mailbox) {
						res = spy_sayname(chan, local_mailbox, local_context);
					} else {
						res = -1;
					}
				}
				if (!ast_test_flag(flags, OPTION_NAME) || res < 0) {
					int num;
					if (!ast_test_flag(flags, OPTION_NOTECH)) {
						if (ast_fileexists(peer_name, NULL, NULL) > 0) {
							res = ast_streamfile(chan, peer_name, ast_channel_language(chan));
							if (!res) {
								res = ast_waitstream(chan, "");
							}
							if (res) {
								ast_autochan_destroy(autochan);
								break;
							}
						} else {
							res = ast_say_character_str(chan, peer_name, "", ast_channel_language(chan), AST_SAY_CASE_NONE);
						}
					}
					if (ptr && (num = atoi(ptr))) {
						ast_say_digits(chan, num, "", ast_channel_language(chan));
					}
				}
			}

			res = channel_spy(chan, autochan, &volfactor, fd, user_options, flags, exitcontext);
			num_spyed_upon++;

			if (res == -1) {
				ast_autochan_destroy(autochan);
				iter = ast_channel_iterator_destroy(iter);
				goto exit;
			} else if (res == -2) {
				res = 0;
				ast_autochan_destroy(autochan);
				iter = ast_channel_iterator_destroy(iter);
				goto exit;
			} else if (res > 1 && spec && !ast_test_flag(flags, OPTION_UNIQUEID)) {
				struct ast_channel *next;

				snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);

				if ((next = ast_channel_get_by_name_prefix(nameprefix, strlen(nameprefix)))) {
					next_autochan = ast_autochan_setup(next);
					next = ast_channel_unref(next);
				} else {
					/* stay on this channel, if it is still valid */
					if (!ast_check_hangup(autochan->chan)) {
						next_autochan = ast_autochan_setup(autochan->chan);
					} else {
						/* the channel is gone */
						next_autochan = NULL;
					}
				}
			} else if (res == 0 && ast_test_flag(flags, OPTION_EXITONHANGUP)) {
				ast_autochan_destroy(autochan);
				iter = ast_channel_iterator_destroy(iter);
				goto exit;
			}
		}

		iter = ast_channel_iterator_destroy(iter);

		if (res == -1 || ast_check_hangup(chan))
			break;
		if (ast_test_flag(flags, OPTION_STOP) && !next_autochan) {
			break;
		}
	}
exit:

	ast_clear_flag(ast_channel_flags(chan), AST_FLAG_SPYING);

	ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);

	return res;
}