2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * Jonathan Rose <jrose@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Call Parking Applications
23 * \author Jonathan Rose <jrose@digium.com>
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
30 #include "res_parking.h"
31 #include "asterisk/config.h"
32 #include "asterisk/config_options.h"
33 #include "asterisk/event.h"
34 #include "asterisk/utils.h"
35 #include "asterisk/astobj2.h"
36 #include "asterisk/features.h"
37 #include "asterisk/module.h"
38 #include "asterisk/app.h"
39 #include "asterisk/say.h"
40 #include "asterisk/features.h"
41 #include "asterisk/bridging_basic.h"
44 <application name="Park" language="en_US">
49 <parameter name="parking_lot_name">
50 <para>Specify in which parking lot to park a call.</para>
51 <para>The parking lot used is selected in the following order:</para>
52 <para>1) parking_lot_name option to this application</para>
53 <para>2) <variable>PARKINGLOT</variable> variable</para>
54 <para>3) <literal>CHANNEL(parkinglot)</literal> function
55 (Possibly preset by the channel driver.)</para>
56 <para>4) Default parking lot.</para>
58 <parameter name="options">
59 <para>A list of options for this parked call.</para>
62 <para>Send ringing instead of MOH to the parked call.</para>
65 <para>Randomize the selection of a parking space.</para>
68 <para>Silence announcement of the parking space number.</para>
70 <option name="c" argsep=",">
71 <argument name="context" required="false" />
72 <argument name="extension" required="false" />
73 <argument name="priority" required="true" />
74 <para>If the parking times out, go to this place in the dialplan
75 instead of where the parking lot defines the call should go.
79 <argument name="duration" required="true" />
80 <para>Use a timeout of <literal>duration</literal> seconds instead
81 of the timeout specified by the parking lot.</para>
87 <para>Used to park yourself (typically in combination with an attended
88 transfer to know the parking space).</para>
89 <para>If you set the <variable>PARKINGEXTEN</variable> variable to a
90 parking space extension in the parking lot, Park() will attempt to park the
91 call on that extension. If the extension is already in use then execution
92 will continue at the next priority.
96 <ref type="application">ParkedCall</ref>
100 <application name="ParkedCall" language="en_US">
102 Retrieve a parked call.
105 <parameter name="parking_lot_name">
106 <para>Specify from which parking lot to retrieve a parked call.</para>
107 <para>The parking lot used is selected in the following order:</para>
108 <para>1) parking_lot_name option</para>
109 <para>2) <variable>PARKINGLOT</variable> variable</para>
110 <para>3) <literal>CHANNEL(parkinglot)</literal> function
111 (Possibly preset by the channel driver.)</para>
112 <para>4) Default parking lot.</para>
114 <parameter name="parking_space">
115 <para>Parking space to retrieve a parked call from.
116 If not provided then the first available parked call in the
117 parking lot will be retrieved.</para>
121 <para>Used to retrieve a parked call from a parking lot.</para>
123 <para>If a parking lot's parkext option is set, then Parking lots
124 will automatically create and manage dialplan extensions in
125 the parking lot context. If that is the case then you will not
126 need to manage parking extensions yourself, just include the
127 parking context of the parking lot.</para>
131 <ref type="application">Park</ref>
135 <application name="ParkAndAnnounce" language="en_US">
140 <parameter name="parking_lot_name">
141 <para>Specify in which parking lot to park a call.</para>
142 <para>The parking lot used is selected in the following order:</para>
143 <para>1) parking_lot_name option to this application</para>
144 <para>2) <variable>PARKINGLOT</variable> variable</para>
145 <para>3) <literal>CHANNEL(parkinglot)</literal> function
146 (Possibly preset by the channel driver.)</para>
147 <para>4) Default parking lot.</para>
149 <parameter name="options">
150 <para>A list of options for this parked call.</para>
153 <para>Send ringing instead of MOH to the parked call.</para>
156 <para>Randomize the selection of a parking space.</para>
158 <option name="c" argsep=",">
159 <argument name="context" required="false" />
160 <argument name="extension" required="false" />
161 <argument name="priority" required="true" />
162 <para>If the parking times out, go to this place in the dialplan
163 instead of where the parking lot defines the call should go.
167 <argument name="duration" required="true" />
168 <para>Use a timeout of <literal>duration</literal> seconds instead
169 of the timeout specified by the parking lot.</para>
173 <parameter name="announce_template" required="true" argsep=":">
174 <argument name="announce" required="true">
175 <para>Colon-separated list of files to announce. The word
176 <literal>PARKED</literal> will be replaced by a say_digits of the extension in which
177 the call is parked.</para>
179 <argument name="announce1" multiple="true" />
181 <parameter name="dial" required="true">
182 <para>The app_dial style resource to call to make the
183 announcement. Console/dsp calls the console.</para>
187 <para>Park a call into the parkinglot and announce the call to another channel.</para>
188 <para>The variable <variable>PARKEDAT</variable> will contain the parking extension
189 into which the call was placed. Use with the Local channel to allow the dialplan to make
190 use of this information.</para>
193 <ref type="application">Park</ref>
194 <ref type="application">ParkedCall</ref>
204 OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
208 MUXFLAG_RINGING = (1 << 0),
209 MUXFLAG_RANDOMIZE = (1 << 1),
210 MUXFLAG_NOANNOUNCE = (1 << 2),
211 MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
212 MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
215 AST_APP_OPTIONS(park_opts, {
216 AST_APP_OPTION('r', MUXFLAG_RINGING),
217 AST_APP_OPTION('R', MUXFLAG_RANDOMIZE),
218 AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
219 AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK),
220 AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT),
223 static int apply_option_timeout (int *var, char *timeout_arg)
225 if (ast_strlen_zero(timeout_arg)) {
226 ast_log(LOG_ERROR, "No duration value provided for the timeout ('t') option.\n");
230 if (sscanf(timeout_arg, "%d", var) != 1 || *var < 0) {
231 ast_log(LOG_ERROR, "Duration value provided for timeout ('t') option must be 0 or greater.\n");
238 static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, char **comeback_override, char **lot_name)
241 struct ast_flags flags = { 0 };
243 AST_DECLARE_APP_ARGS(args,
244 AST_APP_ARG(lot_name);
245 AST_APP_ARG(options);
246 AST_APP_ARG(other); /* Any remaining unused arguments */
249 parse = ast_strdupa(data);
250 AST_STANDARD_APP_ARGS(args, parse);
253 char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
254 ast_app_parse_options(park_opts, &flags, opts, args.options);
255 if (ast_test_flag(&flags, MUXFLAG_TIMEOUT_OVERRIDE)) {
256 if (apply_option_timeout(time_limit, opts[OPT_ARG_TIMEOUT])) {
261 if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) {
262 *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]);
265 if (ast_test_flag(&flags, MUXFLAG_NOANNOUNCE)) {
266 if (disable_announce) {
267 *disable_announce = 1;
271 if (ast_test_flag(&flags, MUXFLAG_RINGING)) {
275 if (ast_test_flag(&flags, MUXFLAG_RANDOMIZE)) {
280 if (!ast_strlen_zero(args.lot_name)) {
281 *lot_name = ast_strdup(args.lot_name);
287 static void park_common_datastore_destroy(void *data)
289 struct park_common_datastore *datastore = data;
290 ast_free(datastore->parker_uuid);
291 ast_free(datastore->comeback_override);
295 static const struct ast_datastore_info park_common_info = {
296 .type = "park entry data",
297 .destroy = park_common_datastore_destroy,
300 static void wipe_park_common_datastore(struct ast_channel *chan)
302 struct ast_datastore *datastore;
304 ast_channel_lock(chan);
305 datastore = ast_channel_datastore_find(chan, &park_common_info, NULL);
307 ast_channel_datastore_remove(chan, datastore);
308 ast_datastore_free(datastore);
310 ast_channel_unlock(chan);
313 static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce)
315 struct ast_datastore *datastore = NULL;
316 struct park_common_datastore *park_datastore;
318 wipe_park_common_datastore(parkee);
320 if (!(datastore = ast_datastore_alloc(&park_common_info, NULL))) {
324 if (!(park_datastore = ast_calloc(1, sizeof(*park_datastore)))) {
325 ast_datastore_free(datastore);
329 park_datastore->parker_uuid = ast_strdup(parker_uuid);
330 park_datastore->randomize = randomize;
331 park_datastore->time_limit = time_limit;
332 park_datastore->silence_announce = silence_announce;
334 if (comeback_override) {
335 park_datastore->comeback_override = ast_strdup(comeback_override);
339 datastore->data = park_datastore;
340 ast_channel_lock(parkee);
341 ast_channel_datastore_add(parkee, datastore);
342 ast_channel_unlock(parkee);
347 void get_park_common_datastore_data(struct ast_channel *parkee, char **parker_uuid, char **comeback_override,
348 int *randomize, int *time_limit, int *silence_announce)
350 struct ast_datastore *datastore;
351 struct park_common_datastore *data;
353 ast_channel_lock(parkee);
354 if (!(datastore = ast_channel_datastore_find(parkee, &park_common_info, NULL))) {
355 ast_channel_unlock(parkee);
359 data = datastore->data;
362 /* This data should always be populated if this datastore was appended to the channel */
366 *parker_uuid = ast_strdup(data->parker_uuid);
367 *randomize = data->randomize;
368 *time_limit = data->time_limit;
369 *silence_announce = data->silence_announce;
371 if (data->comeback_override) {
372 *comeback_override = ast_strdup(data->comeback_override);
375 ast_channel_unlock(parkee);
378 struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
379 const char *lot_name, const char *comeback_override,
380 int use_ringing, int randomize, int time_limit, int silence_announcements)
382 struct ast_bridge *parking_bridge;
383 RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
385 /* If the name of the parking lot isn't specified in the arguments, find it based on the channel. */
386 if (ast_strlen_zero(lot_name)) {
387 ast_channel_lock(parker);
388 lot_name = ast_strdupa(find_channel_parking_lot_name(parker));
389 ast_channel_unlock(parker);
392 lot = parking_lot_find_by_name(lot_name);
394 lot = parking_create_dynamic_lot(lot_name, parkee);
398 ast_log(LOG_ERROR, "Could not find parking lot: '%s'\n", lot_name);
403 parking_bridge = parking_lot_get_bridge(lot);
406 if (!parking_bridge) {
410 /* Apply relevant bridge roles and such to the parking channel */
411 parking_channel_set_roles(parkee, lot, use_ringing);
412 setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
413 silence_announcements);
414 return parking_bridge;
417 struct ast_bridge *park_application_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data,
418 int *silence_announcements)
424 RAII_VAR(char *, comeback_override, NULL, ast_free);
425 RAII_VAR(char *, lot_name_app_arg, NULL, ast_free);
428 park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
431 return park_common_setup(parkee, parker, lot_name_app_arg, comeback_override, use_ringing,
432 randomize, time_limit, silence_announcements ? *silence_announcements : 0);
436 /* XXX BUGBUG - determining the parker when transferred to deep park priority
437 * Currently all parking by the park application is treated as calls parking themselves.
438 * However, it's possible for calls to be transferred here when the Park application is
439 * set after the first priority of an extension. In that case, there used to be a variable
440 * (BLINDTRANSFER) set indicating which channel placed that call here.
442 * If BLINDTRANSFER is set, this channel name will need to be referenced in Park events
443 * generated by stasis. Ideally we would get a whole channel snapshot and use that for the
444 * parker, but that would likely require applying the channel snapshot to a channel datastore
445 * on all transfers. Alternatively just the name of the parking channel could be applied along
446 * with an indication that it's dead.
448 int park_app_exec(struct ast_channel *chan, const char *data)
450 RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
452 struct ast_bridge_features chan_features;
454 int silence_announcements = 0;
455 const char *blind_transfer;
457 /* Answer the channel if needed */
458 if (ast_channel_state(chan) != AST_STATE_UP) {
462 ast_channel_lock(chan);
463 if ((blind_transfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
464 blind_transfer = ast_strdupa(blind_transfer);
466 ast_channel_unlock(chan);
468 /* Handle the common parking setup stuff */
469 if (!(parking_bridge = park_application_setup(chan, chan, data, &silence_announcements))) {
470 if (!silence_announcements && !blind_transfer) {
471 ast_stream_and_wait(chan, "pbx-parkingfailed", "");
476 /* Initialize bridge features for the channel. */
477 res = ast_bridge_features_init(&chan_features);
479 ast_bridge_features_cleanup(&chan_features);
483 /* Now for the fun part... park it! */
484 ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
487 * If the bridge was broken for a hangup that isn't real, then
488 * don't run the h extension, because the channel isn't really
489 * hung up. This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
493 ast_channel_lock(chan);
494 if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
497 ast_channel_unlock(chan);
499 ast_bridge_features_cleanup(&chan_features);
504 /* Retrieve a parked call */
506 int parked_call_app_exec(struct ast_channel *chan, const char *data)
508 RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
509 RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup); /* Parked user being retrieved */
510 RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
511 struct ast_bridge *retrieval_bridge;
513 int target_space = -1;
514 struct ast_bridge_features chan_features;
518 AST_DECLARE_APP_ARGS(args,
519 AST_APP_ARG(lot_name);
520 AST_APP_ARG(parking_space);
521 AST_APP_ARG(other); /* Any remaining unused arguments */
524 parse = ast_strdupa(data);
525 AST_STANDARD_APP_ARGS(args, parse);
527 /* Answer the channel if needed */
528 if (ast_channel_state(chan) != AST_STATE_UP) {
532 lot_name = args.lot_name;
534 /* If the name of the parking lot isn't in the arguments, find it based on the channel. */
535 if (ast_strlen_zero(lot_name)) {
536 ast_channel_lock(chan);
537 lot_name = ast_strdupa(find_channel_parking_lot_name(chan));
538 ast_channel_unlock(chan);
541 lot = parking_lot_find_by_name(lot_name);
544 ast_log(LOG_ERROR, "Could not find the requested parking lot\n");
545 ast_stream_and_wait(chan, "pbx-invalidpark", "");
549 if (!ast_strlen_zero(args.parking_space)) {
550 if (sscanf(args.parking_space, "%d", &target_space) != 1 || target_space < 0) {
551 ast_stream_and_wait(chan, "pbx-invalidpark", "");
552 ast_log(LOG_ERROR, "value '%s' for parking_space argument is invalid. Must be an integer greater than 0.\n", args.parking_space);
557 /* Attempt to get the parked user from the parking lot */
558 pu = parking_lot_retrieve_parked_user(lot, target_space);
560 ast_stream_and_wait(chan, "pbx-invalidpark", "");
564 /* The parked call needs to know who is retrieving it before we move it out of the parking bridge */
565 pu->retriever = ast_channel_snapshot_create(chan);
568 retrieval_bridge = ast_bridge_basic_new();
569 if (!retrieval_bridge) {
573 /* Move the parkee into the new bridge */
574 if (ast_bridge_move(retrieval_bridge, lot->parking_bridge, pu->chan, NULL, 0)) {
575 ast_bridge_destroy(retrieval_bridge);
579 /* Initialize our bridge features */
580 res = ast_bridge_features_init(&chan_features);
582 ast_bridge_destroy(retrieval_bridge);
583 ast_bridge_features_cleanup(&chan_features);
587 /* Set the features */
588 parked_call_retrieve_enable_features(chan, lot, AST_FEATURE_FLAG_BYCALLER);
590 /* If the parkedplay option is set for the caller to hear, play that tone now. */
591 if (lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLER) {
592 ast_stream_and_wait(chan, lot->cfg->courtesytone, NULL);
595 /* Now we should try to join the new bridge ourselves... */
596 ast_bridge_join(retrieval_bridge, chan, NULL, &chan_features, NULL, 1);
598 ast_bridge_features_cleanup(&chan_features);
603 struct park_announce_subscription_data {
606 char *announce_string;
609 static void park_announce_subscription_data_destroy(void *data)
611 struct park_announce_subscription_data *pa_data = data;
612 ast_free(pa_data->parkee_uuid);
613 ast_free(pa_data->dial_string);
614 ast_free(pa_data->announce_string);
618 static struct park_announce_subscription_data *park_announce_subscription_data_create(const char *parkee_uuid,
619 const char *dial_string,
620 const char *announce_string)
622 struct park_announce_subscription_data *pa_data;
624 if (!(pa_data = ast_calloc(1, sizeof(*pa_data)))) {
628 if (!(pa_data->parkee_uuid = ast_strdup(parkee_uuid))
629 || !(pa_data->dial_string = ast_strdup(dial_string))
630 || !(pa_data->announce_string = ast_strdup(announce_string))) {
631 park_announce_subscription_data_destroy(pa_data);
638 static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
640 struct ast_channel *dchan;
641 struct outgoing_helper oh = { 0, };
643 struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
647 struct ast_format tmpfmt;
649 dial_tech = strsep(&dial_string, "/");
650 ast_verb(3, "Dial Tech,String: (%s,%s)\n", dial_tech, dial_string);
653 ast_log(LOG_WARNING, "PARK: Failed to announce park.\n");
654 goto announce_cleanup;
656 ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
658 snprintf(buf, sizeof(buf), "%d", parkingspace);
659 oh.vars = ast_variable_new("_PARKEDAT", buf, "");
660 dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, dial_string, 30000,
662 parkee_snapshot->caller_number,
663 parkee_snapshot->caller_name,
666 ast_variables_destroy(oh.vars);
668 ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
669 goto announce_cleanup;
672 ast_verb(4, "Announce Template: %s\n", announce_string);
674 for (cur_announce = strsep(&announce_string, ":"); cur_announce; cur_announce = strsep(&announce_string, ":")) {
675 ast_verb(4, "Announce:%s\n", cur_announce);
676 if (!strcmp(cur_announce, "PARKED")) {
677 ast_say_digits(dchan, parkingspace, "", ast_channel_language(dchan));
679 int dres = ast_streamfile(dchan, cur_announce, ast_channel_language(dchan));
681 dres = ast_waitstream(dchan, "");
683 ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", cur_announce, ast_channel_name(dchan));
688 ast_stopstream(dchan);
692 cap_slin = ast_format_cap_destroy(cap_slin);
695 static void park_announce_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
697 struct park_announce_subscription_data *pa_data = data;
698 char *dial_string = pa_data->dial_string;
700 struct ast_parked_call_payload *payload = stasis_message_data(message);
702 if (stasis_subscription_final_message(sub, message)) {
703 park_announce_subscription_data_destroy(data);
707 if (payload->event_type != PARKED_CALL) {
708 /* We are only concerned with calls parked */
712 if (strcmp(payload->parkee->uniqueid, pa_data->parkee_uuid)) {
713 /* We are only concerned with the parkee we are subscribed for. */
717 if (!ast_strlen_zero(dial_string)) {
718 announce_to_dial(dial_string, pa_data->announce_string, payload->parkingspace, payload->parkee);
721 *dial_string = '\0'; /* If we observe this dial string on a second pass, we don't want to do anything with it. */
724 int park_and_announce_app_exec(struct ast_channel *chan, const char *data)
726 struct ast_bridge_features chan_features;
729 int silence_announcements = 1;
731 struct stasis_subscription *parking_subscription;
732 struct park_announce_subscription_data *pa_data;
734 RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
736 AST_DECLARE_APP_ARGS(args,
737 AST_APP_ARG(lot_name);
738 AST_APP_ARG(options);
739 AST_APP_ARG(announce_template);
741 AST_APP_ARG(others);/* Any remaining unused arguments */
744 if (ast_strlen_zero(data)) {
745 ast_log(LOG_ERROR, "ParkAndAnnounce has required arguments. No arguments were provided.\n");
749 parse = ast_strdupa(data);
750 AST_STANDARD_APP_ARGS(args, parse);
752 if (ast_strlen_zero(args.announce_template)) {
753 /* improperly configured arguments for the application */
754 ast_log(LOG_ERROR, "ParkAndAnnounce requires the announce_template argument.\n");
758 if (ast_strlen_zero(args.dial)) {
759 /* improperly configured arguments */
760 ast_log(LOG_ERROR, "ParkAndAnnounce requires the dial argument.\n");
764 if (!strchr(args.dial, '/')) {
765 ast_log(LOG_ERROR, "ParkAndAnnounce dial string '%s' is improperly formed.\n", args.dial);
769 /* Handle the common parking setup stuff */
770 if (!(parking_bridge = park_application_setup(chan, chan, data, &silence_announcements))) {
774 /* Initialize bridge features for the channel. */
775 res = ast_bridge_features_init(&chan_features);
777 ast_bridge_features_cleanup(&chan_features);
781 /* subscribe to the parking message so that we can announce once it is parked */
782 pa_data = park_announce_subscription_data_create(ast_channel_uniqueid(chan), args.dial, args.announce_template);
787 if (!(parking_subscription = stasis_subscribe(ast_parking_topic(), park_announce_update_cb, pa_data))) {
788 /* Failed to create subscription */
789 park_announce_subscription_data_destroy(pa_data);
793 /* Now for the fun part... park it! */
794 ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
796 /* Toss the subscription since we aren't bridged at this point. */
797 stasis_unsubscribe(parking_subscription);
800 * If the bridge was broken for a hangup that isn't real, then
801 * don't run the h extension, because the channel isn't really
802 * hung up. This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
806 ast_channel_lock(chan);
807 if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
810 ast_channel_unlock(chan);
812 ast_bridge_features_cleanup(&chan_features);