/*! * \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; }
/*! * \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; }