1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015 |
- /*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2013, Digium, Inc.
- *
- * Jonathan Rose <jrose@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
- /*! \file
- *
- * \brief Call Parking Applications
- *
- * \author Jonathan Rose <jrose@digium.com>
- */
- #include "asterisk.h"
- ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
- #include "res_parking.h"
- #include "asterisk/config.h"
- #include "asterisk/config_options.h"
- #include "asterisk/utils.h"
- #include "asterisk/astobj2.h"
- #include "asterisk/features.h"
- #include "asterisk/module.h"
- #include "asterisk/app.h"
- #include "asterisk/say.h"
- #include "asterisk/bridge_basic.h"
- #include "asterisk/format_cache.h"
- /*** DOCUMENTATION
- <application name="Park" language="en_US">
- <synopsis>
- Park yourself.
- </synopsis>
- <syntax>
- <parameter name="parking_lot_name">
- <para>Specify in which parking lot to park a call.</para>
- <para>The parking lot used is selected in the following order:</para>
- <para>1) parking_lot_name option to this application</para>
- <para>2) <variable>PARKINGLOT</variable> variable</para>
- <para>3) <literal>CHANNEL(parkinglot)</literal> function
- (Possibly preset by the channel driver.)</para>
- <para>4) Default parking lot.</para>
- </parameter>
- <parameter name="options">
- <para>A list of options for this parked call.</para>
- <optionlist>
- <option name="r">
- <para>Send ringing instead of MOH to the parked call.</para>
- </option>
- <option name="R">
- <para>Randomize the selection of a parking space.</para>
- </option>
- <option name="s">
- <para>Silence announcement of the parking space number.</para>
- </option>
- <option name="c" argsep=",">
- <argument name="context" required="false" />
- <argument name="extension" required="false" />
- <argument name="priority" required="true" />
- <para>If the parking times out, go to this place in the dialplan
- instead of where the parking lot defines the call should go.
- </para>
- </option>
- <option name="t">
- <argument name="duration" required="true" />
- <para>Use a timeout of <literal>duration</literal> seconds instead
- of the timeout specified by the parking lot.</para>
- </option>
- </optionlist>
- </parameter>
- </syntax>
- <description>
- <para>Used to park yourself (typically in combination with an attended
- transfer to know the parking space).</para>
- <para>If you set the <variable>PARKINGEXTEN</variable> variable to a
- parking space extension in the parking lot, Park() will attempt to park the
- call on that extension. If the extension is already in use then execution
- will continue at the next priority.
- </para>
- <para>If the <literal>parkeddynamic</literal> option is enabled in
- <filename>res_parking.conf</filename> the following variables can be
- used to dynamically create new parking lots. When using dynamic parking
- lots, be aware of the conditions as explained in the notes section
- below.
- </para>
- <para>The <variable>PARKINGDYNAMIC</variable> variable specifies the
- parking lot to use as a template to create a dynamic parking lot. It
- is an error to specify a non-existent parking lot for the template.
- If not set then the default parking lot is used as the template.
- </para>
- <para>The <variable>PARKINGDYNCONTEXT</variable> variable specifies the
- dialplan context to use for the newly created dynamic parking lot. If
- not set then the context from the parking lot template is used. The
- context is created if it does not already exist and the new parking lot
- needs to create extensions.
- </para>
- <para>The <variable>PARKINGDYNEXTEN</variable> variable specifies the
- <literal>parkext</literal> to use for the newly created dynamic
- parking lot. If not set then the <literal>parkext</literal> is used from
- the parking lot template. If the template does not specify a
- <literal>parkext</literal> then no extensions are created for the newly
- created parking lot. The dynamic parking lot cannot be created if it
- needs to create extensions that overlap existing parking lot extensions.
- The only exception to this is for the <literal>parkext</literal>
- extension and only if neither of the overlaping parking lot's
- <literal>parkext</literal> is exclusive.
- </para>
- <para>The <variable>PARKINGDYNPOS</variable> variable specifies the
- parking positions to use for the newly created dynamic parking lot. If
- not set then the <literal>parkpos</literal> from the parking lot template
- is used.
- </para>
- <note>
- <para>This application must be used as the first extension priority
- to be recognized as a parking access extension for blind transfers.
- Blind transfers and the DTMF one-touch parking feature need this
- distinction to operate properly. The parking access extension in
- this case is treated like a dialplan hint.
- </para>
- </note>
- </description>
- <see-also>
- <ref type="application">ParkedCall</ref>
- </see-also>
- </application>
- <application name="ParkedCall" language="en_US">
- <synopsis>
- Retrieve a parked call.
- </synopsis>
- <syntax>
- <parameter name="parking_lot_name">
- <para>Specify from which parking lot to retrieve a parked call.</para>
- <para>The parking lot used is selected in the following order:</para>
- <para>1) parking_lot_name option</para>
- <para>2) <variable>PARKINGLOT</variable> variable</para>
- <para>3) <literal>CHANNEL(parkinglot)</literal> function
- (Possibly preset by the channel driver.)</para>
- <para>4) Default parking lot.</para>
- </parameter>
- <parameter name="parking_space">
- <para>Parking space to retrieve a parked call from.
- If not provided then the first available parked call in the
- parking lot will be retrieved.</para>
- </parameter>
- </syntax>
- <description>
- <para>Used to retrieve a parked call from a parking lot.</para>
- <note>
- <para>If a parking lot's parkext option is set, then Parking lots
- will automatically create and manage dialplan extensions in
- the parking lot context. If that is the case then you will not
- need to manage parking extensions yourself, just include the
- parking context of the parking lot.</para>
- </note>
- </description>
- <see-also>
- <ref type="application">Park</ref>
- </see-also>
- </application>
- <application name="ParkAndAnnounce" language="en_US">
- <synopsis>
- Park and Announce.
- </synopsis>
- <syntax>
- <parameter name="parking_lot_name">
- <para>Specify in which parking lot to park a call.</para>
- <para>The parking lot used is selected in the following order:</para>
- <para>1) parking_lot_name option to this application</para>
- <para>2) <variable>PARKINGLOT</variable> variable</para>
- <para>3) <literal>CHANNEL(parkinglot)</literal> function
- (Possibly preset by the channel driver.)</para>
- <para>4) Default parking lot.</para>
- </parameter>
- <parameter name="options">
- <para>A list of options for this parked call.</para>
- <optionlist>
- <option name="r">
- <para>Send ringing instead of MOH to the parked call.</para>
- </option>
- <option name="R">
- <para>Randomize the selection of a parking space.</para>
- </option>
- <option name="c" argsep=",">
- <argument name="context" required="false" />
- <argument name="extension" required="false" />
- <argument name="priority" required="true" />
- <para>If the parking times out, go to this place in the dialplan
- instead of where the parking lot defines the call should go.
- </para>
- </option>
- <option name="t">
- <argument name="duration" required="true" />
- <para>Use a timeout of <literal>duration</literal> seconds instead
- of the timeout specified by the parking lot.</para>
- </option>
- </optionlist>
- </parameter>
- <parameter name="announce_template" required="true" argsep=":">
- <argument name="announce" required="true">
- <para>Colon-separated list of files to announce. The word
- <literal>PARKED</literal> will be replaced by a say_digits of the extension in which
- the call is parked.</para>
- </argument>
- <argument name="announce1" multiple="true" />
- </parameter>
- <parameter name="dial" required="true">
- <para>The app_dial style resource to call to make the
- announcement. Console/dsp calls the console.</para>
- </parameter>
- </syntax>
- <description>
- <para>Park a call into the parkinglot and announce the call to another channel.</para>
- <para>The variable <variable>PARKEDAT</variable> will contain the parking extension
- into which the call was placed. Use with the Local channel to allow the dialplan to make
- use of this information.</para>
- </description>
- <see-also>
- <ref type="application">Park</ref>
- <ref type="application">ParkedCall</ref>
- </see-also>
- </application>
- ***/
- #define PARK_AND_ANNOUNCE_APPLICATION "ParkAndAnnounce"
- /* Park a call */
- enum park_args {
- OPT_ARG_COMEBACK,
- OPT_ARG_TIMEOUT,
- OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
- };
- enum park_flags {
- MUXFLAG_RINGING = (1 << 0),
- MUXFLAG_RANDOMIZE = (1 << 1),
- MUXFLAG_NOANNOUNCE = (1 << 2),
- MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
- MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
- };
- AST_APP_OPTIONS(park_opts, {
- AST_APP_OPTION('r', MUXFLAG_RINGING),
- AST_APP_OPTION('R', MUXFLAG_RANDOMIZE),
- AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
- AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK),
- AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT),
- });
- static int apply_option_timeout (int *var, char *timeout_arg)
- {
- if (ast_strlen_zero(timeout_arg)) {
- ast_log(LOG_ERROR, "No duration value provided for the timeout ('t') option.\n");
- return -1;
- }
- if (sscanf(timeout_arg, "%d", var) != 1 || *var < 0) {
- ast_log(LOG_ERROR, "Duration value provided for timeout ('t') option must be 0 or greater.\n");
- return -1;
- }
- return 0;
- }
- 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)
- {
- char *parse;
- struct ast_flags flags = { 0 };
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(lot_name);
- AST_APP_ARG(options);
- AST_APP_ARG(other); /* Any remaining unused arguments */
- );
- parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(args, parse);
- if (args.options) {
- char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
- ast_app_parse_options(park_opts, &flags, opts, args.options);
- if (ast_test_flag(&flags, MUXFLAG_TIMEOUT_OVERRIDE)) {
- if (apply_option_timeout(time_limit, opts[OPT_ARG_TIMEOUT])) {
- return -1;
- }
- }
- if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) {
- *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]);
- }
- if (ast_test_flag(&flags, MUXFLAG_NOANNOUNCE)) {
- if (disable_announce) {
- *disable_announce = 1;
- }
- }
- if (ast_test_flag(&flags, MUXFLAG_RINGING)) {
- *use_ringing = 1;
- }
- if (ast_test_flag(&flags, MUXFLAG_RANDOMIZE)) {
- *randomize = 1;
- }
- }
- if (!ast_strlen_zero(args.lot_name)) {
- *lot_name = ast_strdup(args.lot_name);
- }
- return 0;
- }
- void park_common_datastore_free(struct park_common_datastore *datastore)
- {
- if (!datastore) {
- return;
- }
- ast_free(datastore->parker_uuid);
- ast_free(datastore->parker_dial_string);
- ast_free(datastore->comeback_override);
- ast_free(datastore);
- }
- static void park_common_datastore_destroy(void *data)
- {
- struct park_common_datastore *datastore = data;
- park_common_datastore_free(datastore);
- }
- static const struct ast_datastore_info park_common_info = {
- .type = "park entry data",
- .destroy = park_common_datastore_destroy,
- };
- static void wipe_park_common_datastore(struct ast_channel *chan)
- {
- struct ast_datastore *datastore;
- ast_channel_lock(chan);
- datastore = ast_channel_datastore_find(chan, &park_common_info, NULL);
- if (datastore) {
- ast_channel_datastore_remove(chan, datastore);
- ast_datastore_free(datastore);
- }
- ast_channel_unlock(chan);
- }
- 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)
- {
- struct ast_datastore *datastore = NULL;
- struct park_common_datastore *park_datastore;
- const char *attended_transfer;
- const char *blind_transfer;
- char *parker_dial_string = NULL;
- wipe_park_common_datastore(parkee);
- if (!(datastore = ast_datastore_alloc(&park_common_info, NULL))) {
- return -1;
- }
- if (!(park_datastore = ast_calloc(1, sizeof(*park_datastore)))) {
- ast_datastore_free(datastore);
- return -1;
- }
- datastore->data = park_datastore;
- park_datastore->parker_uuid = ast_strdup(parker_uuid);
- if (!park_datastore->parker_uuid) {
- ast_datastore_free(datastore);
- return -1;
- }
- ast_channel_lock(parkee);
- attended_transfer = pbx_builtin_getvar_helper(parkee, "ATTENDEDTRANSFER");
- blind_transfer = pbx_builtin_getvar_helper(parkee, "BLINDTRANSFER");
- if (!ast_strlen_zero(attended_transfer)) {
- parker_dial_string = ast_strdupa(attended_transfer);
- } else if (!ast_strlen_zero(blind_transfer)) {
- parker_dial_string = ast_strdupa(blind_transfer);
- /* Ensure that attended_transfer is NULL and not an empty string. */
- attended_transfer = NULL;
- }
- ast_channel_unlock(parkee);
- if (!ast_strlen_zero(parker_dial_string)) {
- ast_channel_name_to_dial_string(parker_dial_string);
- ast_verb(4, "Setting Parker dial string to %s from %s value\n",
- parker_dial_string,
- attended_transfer ? "ATTENDEDTRANSFER" : "BLINDTRANSFER");
- park_datastore->parker_dial_string = ast_strdup(parker_dial_string);
- if (!park_datastore->parker_dial_string) {
- ast_datastore_free(datastore);
- return -1;
- }
- }
- park_datastore->randomize = randomize;
- park_datastore->time_limit = time_limit;
- park_datastore->silence_announce = silence_announce;
- if (comeback_override) {
- park_datastore->comeback_override = ast_strdup(comeback_override);
- if (!park_datastore->comeback_override) {
- ast_datastore_free(datastore);
- return -1;
- }
- }
- ast_channel_lock(parkee);
- ast_channel_datastore_add(parkee, datastore);
- ast_channel_unlock(parkee);
- return 0;
- }
- struct park_common_datastore *get_park_common_datastore_copy(struct ast_channel *parkee)
- {
- struct ast_datastore *datastore;
- struct park_common_datastore *data;
- struct park_common_datastore *data_copy;
- SCOPED_CHANNELLOCK(lock, parkee);
- if (!(datastore = ast_channel_datastore_find(parkee, &park_common_info, NULL))) {
- return NULL;
- }
- data = datastore->data;
- /* This data should always be populated if this datastore was appended to the channel */
- ast_assert(data != NULL);
- data_copy = ast_calloc(1, sizeof(*data_copy));
- if (!data_copy) {
- return NULL;
- }
- data_copy->parker_uuid = ast_strdup(data->parker_uuid);
- if (!data_copy->parker_uuid) {
- park_common_datastore_free(data_copy);
- return NULL;
- }
- data_copy->randomize = data->randomize;
- data_copy->time_limit = data->time_limit;
- data_copy->silence_announce = data->silence_announce;
- if (data->comeback_override) {
- data_copy->comeback_override = ast_strdup(data->comeback_override);
- if (!data_copy->comeback_override) {
- park_common_datastore_free(data_copy);
- return NULL;
- }
- }
- if (data->parker_dial_string) {
- data_copy->parker_dial_string = ast_strdup(data->parker_dial_string);
- if (!data_copy->parker_dial_string) {
- park_common_datastore_free(data_copy);
- return NULL;
- }
- }
- return data_copy;
- }
- struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
- const char *lot_name, const char *comeback_override,
- int use_ringing, int randomize, int time_limit, int silence_announcements)
- {
- struct ast_bridge *parking_bridge;
- RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
- if (!parker) {
- parker = parkee;
- }
- /* If the name of the parking lot isn't specified in the arguments, find it based on the channel. */
- if (ast_strlen_zero(lot_name)) {
- ast_channel_lock(parker);
- lot_name = ast_strdupa(find_channel_parking_lot_name(parker));
- ast_channel_unlock(parker);
- }
- lot = parking_lot_find_by_name(lot_name);
- if (!lot) {
- lot = parking_create_dynamic_lot(lot_name, parker);
- }
- if (!lot) {
- ast_log(LOG_ERROR, "Could not find parking lot: '%s'\n", lot_name);
- return NULL;
- }
- ao2_lock(lot);
- parking_bridge = parking_lot_get_bridge(lot);
- ao2_unlock(lot);
- if (!parking_bridge) {
- return NULL;
- }
- /* Apply relevant bridge roles and such to the parking channel */
- parking_channel_set_roles(parkee, lot, use_ringing);
- setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
- silence_announcements);
- return parking_bridge;
- }
- struct ast_bridge *park_application_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data,
- int *silence_announcements)
- {
- int use_ringing = 0;
- int randomize = 0;
- int time_limit = -1;
- RAII_VAR(char *, comeback_override, NULL, ast_free);
- RAII_VAR(char *, lot_name_app_arg, NULL, ast_free);
- if (app_data) {
- park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
- }
- return park_common_setup(parkee, parker, lot_name_app_arg, comeback_override, use_ringing,
- randomize, time_limit, silence_announcements ? *silence_announcements : 0);
- }
- static int park_app_exec(struct ast_channel *chan, const char *data)
- {
- RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
- struct ast_bridge_features chan_features;
- int res;
- int silence_announcements = 0;
- int blind_transfer;
- /* Answer the channel if needed */
- if (ast_channel_state(chan) != AST_STATE_UP) {
- ast_answer(chan);
- }
- ast_channel_lock(chan);
- blind_transfer = !ast_strlen_zero(pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"));
- ast_channel_unlock(chan);
- /* Handle the common parking setup stuff */
- if (!(parking_bridge = park_application_setup(chan, NULL, data, &silence_announcements))) {
- if (!silence_announcements && !blind_transfer) {
- ast_stream_and_wait(chan, "pbx-parkingfailed", "");
- }
- publish_parked_call_failure(chan);
- return 0;
- }
- /* Initialize bridge features for the channel. */
- res = ast_bridge_features_init(&chan_features);
- if (res) {
- ast_bridge_features_cleanup(&chan_features);
- publish_parked_call_failure(chan);
- return -1;
- }
- /* Now for the fun part... park it! */
- ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
- /*
- * If the bridge was broken for a hangup that isn't real, then
- * don't run the h extension, because the channel isn't really
- * hung up. This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
- */
- res = -1;
- ast_channel_lock(chan);
- if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
- res = 0;
- }
- ast_channel_unlock(chan);
- ast_bridge_features_cleanup(&chan_features);
- return res;
- }
- /* Retrieve a parked call */
- static int parked_call_app_exec(struct ast_channel *chan, const char *data)
- {
- RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
- RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup); /* Parked user being retrieved */
- struct ast_bridge *retrieval_bridge;
- int res;
- int target_space = -1;
- struct ast_bridge_features chan_features;
- char *parse;
- const char *lot_name;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(lot_name);
- AST_APP_ARG(parking_space);
- AST_APP_ARG(other); /* Any remaining unused arguments */
- );
- parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(args, parse);
- /* Answer the channel if needed */
- if (ast_channel_state(chan) != AST_STATE_UP) {
- ast_answer(chan);
- }
- lot_name = args.lot_name;
- /* If the name of the parking lot isn't in the arguments, find it based on the channel. */
- if (ast_strlen_zero(lot_name)) {
- ast_channel_lock(chan);
- lot_name = ast_strdupa(find_channel_parking_lot_name(chan));
- ast_channel_unlock(chan);
- }
- lot = parking_lot_find_by_name(lot_name);
- if (!lot) {
- ast_log(LOG_ERROR, "Could not find the requested parking lot\n");
- ast_stream_and_wait(chan, "pbx-invalidpark", "");
- return -1;
- }
- if (!ast_strlen_zero(args.parking_space)) {
- if (sscanf(args.parking_space, "%d", &target_space) != 1 || target_space < 0) {
- ast_stream_and_wait(chan, "pbx-invalidpark", "");
- ast_log(LOG_ERROR, "value '%s' for parking_space argument is invalid. Must be an integer greater than 0.\n", args.parking_space);
- return -1;
- }
- }
- /* Attempt to get the parked user from the parking lot */
- pu = parking_lot_retrieve_parked_user(lot, target_space);
- if (!pu) {
- ast_stream_and_wait(chan, "pbx-invalidpark", "");
- return -1;
- }
- /* The parked call needs to know who is retrieving it before we move it out of the parking bridge */
- ast_assert(pu->retriever == NULL);
- pu->retriever = ast_channel_snapshot_create(chan);
- /* Create bridge */
- retrieval_bridge = ast_bridge_basic_new();
- if (!retrieval_bridge) {
- return -1;
- }
- /* Move the parkee into the new bridge */
- if (ast_bridge_move(retrieval_bridge, lot->parking_bridge, pu->chan, NULL, 0)) {
- ast_bridge_destroy(retrieval_bridge, 0);
- return -1;
- }
- /* Initialize our bridge features */
- res = ast_bridge_features_init(&chan_features);
- if (res) {
- ast_bridge_destroy(retrieval_bridge, 0);
- ast_bridge_features_cleanup(&chan_features);
- return -1;
- }
- /* Set the features */
- parked_call_retrieve_enable_features(chan, lot, AST_FEATURE_FLAG_BYCALLER);
- /* If the parkedplay option is set for the caller to hear, play that tone now. */
- if (lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLER) {
- ast_stream_and_wait(chan, lot->cfg->courtesytone, NULL);
- }
- /* Now we should try to join the new bridge ourselves... */
- ast_bridge_join(retrieval_bridge, chan, NULL, &chan_features, NULL,
- AST_BRIDGE_JOIN_PASS_REFERENCE);
- ast_bridge_features_cleanup(&chan_features);
- /* Return -1 so that call does not continue in the dialplan. This is to make
- * behavior consistent with Asterisk versions prior to 12.
- */
- return -1;
- }
- struct park_announce_subscription_data {
- char *parkee_uuid;
- char *dial_string;
- char *announce_string;
- };
- static void park_announce_subscription_data_destroy(void *data)
- {
- struct park_announce_subscription_data *pa_data = data;
- ast_free(pa_data->parkee_uuid);
- ast_free(pa_data->dial_string);
- ast_free(pa_data->announce_string);
- ast_free(pa_data);
- }
- static struct park_announce_subscription_data *park_announce_subscription_data_create(const char *parkee_uuid,
- const char *dial_string,
- const char *announce_string)
- {
- struct park_announce_subscription_data *pa_data;
- if (!(pa_data = ast_calloc(1, sizeof(*pa_data)))) {
- return NULL;
- }
- if (!(pa_data->parkee_uuid = ast_strdup(parkee_uuid))
- || !(pa_data->dial_string = ast_strdup(dial_string))
- || !(pa_data->announce_string = ast_strdup(announce_string))) {
- park_announce_subscription_data_destroy(pa_data);
- return NULL;
- }
- return pa_data;
- }
- /*! \internal
- * \brief Gathers inheritable channel variables from a channel by name.
- *
- * \param oh outgoing helper struct we are bestowing inheritable variables to
- * \param channel_id name or uniqueID of the channel to inherit variables from
- *
- * \return Nothing
- */
- static void inherit_channel_vars_from_id(struct outgoing_helper *oh, const char *channel_id)
- {
- struct ast_channel *chan = ast_channel_get_by_name(channel_id);
- struct ast_var_t *current;
- struct ast_variable *newvar;
- const char *varname;
- int vartype;
- if (!chan) {
- /* Already gone */
- return;
- }
- ast_channel_lock(chan);
- AST_LIST_TRAVERSE(ast_channel_varshead((struct ast_channel *) chan), current, entries) {
- varname = ast_var_full_name(current);
- if (!varname) {
- continue;
- }
- vartype = 0;
- if (varname[0] == '_') {
- vartype = 1;
- if (varname[1] == '_') {
- vartype = 2;
- }
- }
- switch (vartype) {
- case 1:
- newvar = ast_variable_new(&varname[1], ast_var_value(current), "");
- break;
- case 2:
- newvar = ast_variable_new(varname, ast_var_value(current), "");
- break;
- default:
- continue;
- }
- if (newvar) {
- ast_debug(1, "Inheriting variable %s from %s.\n",
- newvar->name, ast_channel_name(chan));
- if (oh->vars) {
- newvar->next = oh->vars;
- oh->vars = newvar;
- }
- }
- }
- ast_channel_unlock(chan);
- ast_channel_cleanup(chan);
- }
- static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
- {
- struct ast_channel *dchan;
- struct outgoing_helper oh = { 0, };
- int outstate;
- struct ast_format_cap *cap_slin = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
- char buf[13];
- char *dial_tech;
- char *cur_announce;
- dial_tech = strsep(&dial_string, "/");
- ast_verb(3, "Dial Tech,String: (%s,%s)\n", dial_tech, dial_string);
- if (!cap_slin) {
- ast_log(LOG_WARNING, "PARK: Failed to announce park.\n");
- goto announce_cleanup;
- }
- ast_format_cap_append(cap_slin, ast_format_slin, 0);
- snprintf(buf, sizeof(buf), "%d", parkingspace);
- oh.vars = ast_variable_new("_PARKEDAT", buf, "");
- inherit_channel_vars_from_id(&oh, parkee_snapshot->uniqueid);
- dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, NULL, dial_string, 30000,
- &outstate,
- parkee_snapshot->caller_number,
- parkee_snapshot->caller_name,
- &oh);
- ast_variables_destroy(oh.vars);
- if (!dchan) {
- ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
- goto announce_cleanup;
- }
- ast_verb(4, "Announce Template: %s\n", announce_string);
- for (cur_announce = strsep(&announce_string, ":"); cur_announce; cur_announce = strsep(&announce_string, ":")) {
- ast_verb(4, "Announce:%s\n", cur_announce);
- if (!strcmp(cur_announce, "PARKED")) {
- ast_say_digits(dchan, parkingspace, "", ast_channel_language(dchan));
- } else {
- int dres = ast_streamfile(dchan, cur_announce, ast_channel_language(dchan));
- if (!dres) {
- dres = ast_waitstream(dchan, "");
- } else {
- ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", cur_announce, ast_channel_name(dchan));
- }
- }
- }
- ast_stopstream(dchan);
- ast_hangup(dchan);
- announce_cleanup:
- ao2_cleanup(cap_slin);
- }
- static void park_announce_update_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message)
- {
- struct park_announce_subscription_data *pa_data = data;
- char *dial_string = pa_data->dial_string;
- struct ast_parked_call_payload *payload = stasis_message_data(message);
- if (stasis_subscription_final_message(sub, message)) {
- park_announce_subscription_data_destroy(data);
- return;
- }
- if (ast_parked_call_type() != stasis_message_type(message)) {
- return;
- }
- if (payload->event_type != PARKED_CALL) {
- /* We are only concerned with calls parked */
- return;
- }
- if (strcmp(payload->parkee->uniqueid, pa_data->parkee_uuid)) {
- /* We are only concerned with the parkee we are subscribed for. */
- return;
- }
- if (!ast_strlen_zero(dial_string)) {
- announce_to_dial(dial_string, pa_data->announce_string, payload->parkingspace, payload->parkee);
- }
- *dial_string = '\0'; /* If we observe this dial string on a second pass, we don't want to do anything with it. */
- }
- static int park_and_announce_app_exec(struct ast_channel *chan, const char *data)
- {
- struct ast_bridge_features chan_features;
- char *parse;
- int res;
- int silence_announcements = 1;
- struct stasis_subscription *parking_subscription;
- struct park_announce_subscription_data *pa_data;
- RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(lot_name);
- AST_APP_ARG(options);
- AST_APP_ARG(announce_template);
- AST_APP_ARG(dial);
- AST_APP_ARG(others);/* Any remaining unused arguments */
- );
- if (ast_strlen_zero(data)) {
- ast_log(LOG_ERROR, "ParkAndAnnounce has required arguments. No arguments were provided.\n");
- return -1;
- }
- parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(args, parse);
- if (ast_strlen_zero(args.announce_template)) {
- /* improperly configured arguments for the application */
- ast_log(LOG_ERROR, "ParkAndAnnounce requires the announce_template argument.\n");
- return -1;
- }
- if (ast_strlen_zero(args.dial)) {
- /* improperly configured arguments */
- ast_log(LOG_ERROR, "ParkAndAnnounce requires the dial argument.\n");
- return -1;
- }
- if (!strchr(args.dial, '/')) {
- ast_log(LOG_ERROR, "ParkAndAnnounce dial string '%s' is improperly formed.\n", args.dial);
- return -1;
- }
- /* Handle the common parking setup stuff */
- if (!(parking_bridge = park_application_setup(chan, NULL, data, &silence_announcements))) {
- return 0;
- }
- /* Initialize bridge features for the channel. */
- res = ast_bridge_features_init(&chan_features);
- if (res) {
- ast_bridge_features_cleanup(&chan_features);
- return -1;
- }
- /* subscribe to the parking message so that we can announce once it is parked */
- pa_data = park_announce_subscription_data_create(ast_channel_uniqueid(chan), args.dial, args.announce_template);
- if (!pa_data) {
- return -1;
- }
- if (!(parking_subscription = stasis_subscribe_pool(ast_parking_topic(), park_announce_update_cb, pa_data))) {
- /* Failed to create subscription */
- park_announce_subscription_data_destroy(pa_data);
- return -1;
- }
- stasis_subscription_accept_message_type(parking_subscription, ast_parked_call_type());
- stasis_subscription_accept_message_type(parking_subscription, stasis_subscription_change_type());
- stasis_subscription_set_filter(parking_subscription, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
- /* Now for the fun part... park it! */
- ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
- /* Toss the subscription since we aren't bridged at this point. */
- stasis_unsubscribe(parking_subscription);
- /*
- * If the bridge was broken for a hangup that isn't real, then
- * don't run the h extension, because the channel isn't really
- * hung up. This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
- */
- res = -1;
- ast_channel_lock(chan);
- if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
- res = 0;
- }
- ast_channel_unlock(chan);
- ast_bridge_features_cleanup(&chan_features);
- return res;
- }
- int load_parking_applications(void)
- {
- const struct ast_module_info *ast_module_info = parking_get_module_info();
- if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
- return -1;
- }
- if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
- return -1;
- }
- if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
- return -1;
- }
- return 0;
- }
- void unload_parking_applications(void)
- {
- ast_unregister_application(PARK_APPLICATION);
- ast_unregister_application(PARKED_CALL_APPLICATION);
- ast_unregister_application(PARK_AND_ANNOUNCE_APPLICATION);
- }
|