diff options
author | Richard Mudgett <rmudgett@digium.com> | 2013-05-21 18:00:22 +0000 |
---|---|---|
committer | Richard Mudgett <rmudgett@digium.com> | 2013-05-21 18:00:22 +0000 |
commit | 3d63833bd6c869b7efa383e8dea14be1a6eff998 (patch) | |
tree | 34957dd051b8f67c7cc58a510e24ee3873a61ad4 /main/features.c | |
parent | e1e1cc2deefb92f8b43825f1f34e619354737842 (diff) |
Merge in the bridge_construction branch to make the system use the Bridging API.
Breaks many things until they can be reworked. A partial list:
chan_agent
chan_dahdi, chan_misdn, chan_iax2 native bridging
app_queue
COLP updates
DTMF attended transfers
Protocol attended transfers
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main/features.c')
-rw-r--r-- | main/features.c | 1920 |
1 files changed, 475 insertions, 1445 deletions
diff --git a/main/features.c b/main/features.c index b6cc19164..be872a80d 100644 --- a/main/features.c +++ b/main/features.c @@ -72,6 +72,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/astobj2.h" #include "asterisk/cel.h" #include "asterisk/test.h" +#include "asterisk/bridging.h" +#include "asterisk/bridging_basic.h" /* * Party A - transferee @@ -248,129 +250,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </variablelist> </description> </application> - <application name="ParkedCall" language="en_US"> - <synopsis> - Retrieve a parked call. - </synopsis> - <syntax> - <parameter name="exten"> - <para>Parking space extension to retrieve a parked call. - If not provided then the first available parked call in the - parking lot will be retrieved.</para> - </parameter> - <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> - </syntax> - <description> - <para>Used to retrieve a parked call from a parking lot.</para> - <note> - <para>Parking lots automatically create and manage dialplan extensions in - the parking lot context. You do not need to explicitly use this - application in your dialplan. Instead, all you should do is include the - parking lot context in your dialplan.</para> - </note> - </description> - <see-also> - <ref type="application">Park</ref> - <ref type="application">ParkAndAnnounce</ref> - </see-also> - </application> - <application name="Park" language="en_US"> - <synopsis> - Park yourself. - </synopsis> - <syntax> - <parameter name="timeout"> - <para>A custom parking timeout for this parked call. Value in milliseconds.</para> - </parameter> - <parameter name="return_context"> - <para>The context to return the call to after it times out.</para> - </parameter> - <parameter name="return_exten"> - <para>The extension to return the call to after it times out.</para> - </parameter> - <parameter name="return_priority"> - <para>The priority to return the call to after it times out.</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> - </optionlist> - </parameter> - <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</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> - </syntax> - <description> - <para>Used to park yourself (typically in combination with a supervised - 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 is in use then execution - will continue at the next priority.</para> - <para>If the <literal>parkeddynamic</literal> option is enabled in <filename>features.conf</filename> - the following variables can be used to dynamically create new parking lots.</para> - <para>If you set the <variable>PARKINGDYNAMIC</variable> variable and this parking lot - exists then it will be used as a template for the newly created dynamic lot. Otherwise, - the default parking lot will be used.</para> - <para>If you set the <variable>PARKINGDYNCONTEXT</variable> variable then the newly created dynamic - parking lot will use this context.</para> - <para>If you set the <variable>PARKINGDYNEXTEN</variable> variable then the newly created dynamic - parking lot will use this extension to access the parking lot.</para> - <para>If you set the <variable>PARKINGDYNPOS</variable> variable then the newly created dynamic parking lot - will use those parking postitions.</para> - <note> - <para>This application must be used as the first extension priority - to be recognized as a parking access extension. DTMF transfers - and some channel drivers need this distinction to operate properly. - The parking access extension in this case is treated like a dialplan - hint.</para> - </note> - <note> - <para>Parking lots automatically create and manage dialplan extensions in - the parking lot context. You do not need to explicitly use this - application in your dialplan. Instead, all you should do is include the - parking lot context in your dialplan.</para> - </note> - </description> - <see-also> - <ref type="application">ParkAndAnnounce</ref> - <ref type="application">ParkedCall</ref> - </see-also> - </application> - <manager name="ParkedCalls" language="en_US"> - <synopsis> - List parked calls. - </synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> - </syntax> - <description> - <para>List parked calls.</para> - </description> - </manager> <manager name="Park" language="en_US"> <synopsis> Park a channel. @@ -418,17 +297,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>Bridge together two channels already in the PBX.</para> </description> </manager> - <manager name="Parkinglots" language="en_US"> - <synopsis> - Get a list of parking lots - </synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> - </syntax> - <description> - <para>List all parking lots as a series of AMI events</para> - </description> - </manager> <function name="FEATURE" language="en_US"> <synopsis> Get or set a feature option on a channel. @@ -538,6 +406,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define AST_MAX_WATCHERS 256 #define MAX_DIAL_FEATURE_OPTIONS 30 +/* TODO Scrape all of the parking stuff out of features.c */ + struct feature_group_exten { AST_LIST_ENTRY(feature_group_exten) entry; AST_DECLARE_STRING_FIELDS( @@ -1134,65 +1004,40 @@ static void *bridge_call_thread(void *data) ast_channel_data_set(tobj->peer, "(Empty)"); } - ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig); - if (tobj->return_to_pbx) { - if (!ast_check_hangup(tobj->peer)) { - ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", ast_channel_name(tobj->peer)); - if (ast_pbx_start(tobj->peer)) { - ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", ast_channel_name(tobj->peer)); - ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer); - } - } else { - ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer); - } - if (!ast_check_hangup(tobj->chan)) { - ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", ast_channel_name(tobj->chan)); - if (ast_pbx_start(tobj->chan)) { - ast_log(LOG_WARNING, "FAILED continuing PBX on chan %s\n", ast_channel_name(tobj->chan)); - ast_hangup(tobj->chan); - } - } else { - ast_hangup(tobj->chan); - } - } else { - ast_hangup(tobj->chan); - ast_hangup(tobj->peer); + ast_after_bridge_set_goto(tobj->chan, ast_channel_context(tobj->chan), + ast_channel_exten(tobj->chan), ast_channel_priority(tobj->chan)); + ast_after_bridge_set_goto(tobj->peer, ast_channel_context(tobj->peer), + ast_channel_exten(tobj->peer), ast_channel_priority(tobj->peer)); } + ast_bridge_call(tobj->chan, tobj->peer, &tobj->bconfig); + + ast_after_bridge_goto_run(tobj->chan); + ast_free(tobj); return NULL; } /*! - * \brief create thread for the parked call - * \param data - * - * Create thread and attributes, call bridge_call_thread + * \brief create thread for the bridging call + * \param tobj */ -static void bridge_call_thread_launch(struct ast_bridge_thread_obj *data) +static void bridge_call_thread_launch(struct ast_bridge_thread_obj *tobj) { pthread_t thread; - pthread_attr_t attr; - struct sched_param sched; /* This needs to be unreffed once it has been associated with the new thread. */ - data->callid = ast_read_threadstorage_callid(); - - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - if (ast_pthread_create(&thread, &attr, bridge_call_thread, data)) { - /* Failed to create thread. Ditch the reference to callid. */ - ast_callid_unref(data->callid); - ast_hangup(data->chan); - ast_hangup(data->peer); + tobj->callid = ast_read_threadstorage_callid(); + + if (ast_pthread_create_detached(&thread, NULL, bridge_call_thread, tobj)) { ast_log(LOG_ERROR, "Failed to create bridge_call_thread.\n"); - return; + ast_callid_unref(tobj->callid); + ast_hangup(tobj->chan); + ast_hangup(tobj->peer); + ast_free(tobj); } - pthread_attr_destroy(&attr); - memset(&sched, 0, sizeof(sched)); - pthread_setschedparam(thread, SCHED_RR, &sched); } /*! @@ -2583,11 +2428,6 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p if (ast_async_goto(transferee, transferer_real_context, xferto, 1)) { ast_log(LOG_WARNING, "Async goto failed :-(\n"); res = -1; - } else if (res == AST_FEATURE_RETURN_SUCCESSBREAK) { - /* Don't let the after-bridge code run the h-exten */ - ast_channel_lock(transferee); - ast_set_flag(ast_channel_flags(transferee), AST_FLAG_BRIDGE_HANGUP_DONT); - ast_channel_unlock(transferee); } check_goto_on_transfer(transferer); return res; @@ -2774,8 +2614,6 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st ast_debug(2, "Dial party C result: newchan:%d, outstate:%d\n", !!newchan, outstate); if (!ast_check_hangup(transferer)) { - int hangup_dont = 0; - /* Transferer (party B) is up */ ast_debug(1, "Actually doing an attended transfer.\n"); @@ -2815,31 +2653,11 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT); /* - * ast_bridge_call clears AST_FLAG_BRIDGE_HANGUP_DONT, but we - * don't want that to happen here because the transferer is in - * another bridge already. - */ - if (ast_test_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT)) { - hangup_dont = 1; - } - - /* - * Don't let the after-bridge code run the h-exten. It is the - * wrong bridge to run the h-exten after. - */ - ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT); - - /* * Let party B and C talk as long as they want while party A * languishes in autoservice listening to MOH. */ ast_bridge_call(transferer, newchan, &bconfig); - if (hangup_dont) { - /* Restore the AST_FLAG_BRIDGE_HANGUP_DONT flag */ - ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT); - } - if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) { ast_autoservice_chan_hangup_peer(transferer, newchan); if (ast_stream_and_wait(transferer, xfersound, "")) { @@ -3731,6 +3549,7 @@ static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel return res; } +#if 0//BUGBUG /*! * \brief Check the dynamic features * \param chan,peer,config,code,sense @@ -3777,12 +3596,14 @@ static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer, return res; } +#endif int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature) { return feature_interpret_helper(chan, NULL, NULL, code, 0, NULL, features, FEATURE_INTERPRET_DETECT, feature); } +#if 0//BUGBUG /*! \brief Check if a feature exists */ static int feature_check(struct ast_channel *chan, struct ast_flags *features, char *code) { struct ast_str *chan_dynamic_features; @@ -3801,11 +3622,15 @@ static int feature_check(struct ast_channel *chan, struct ast_flags *features, c return res; } +#endif static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config) { int x; +/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */ +/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */ +/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */ ast_clear_flag(config, AST_FLAGS_ALL); ast_rdlock_call_features(); @@ -4208,37 +4033,22 @@ void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this i { ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan); ast_log(LOG_NOTICE, "CHAN: name: %s; appl: %s; data: %s; contxt: %s; exten: %s; pri: %d;\n", - ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan), ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan)); + ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan), + ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan)); ast_log(LOG_NOTICE, "CHAN: acctcode: %s; dialcontext: %s; amaflags: %x; maccontxt: %s; macexten: %s; macpri: %d;\n", - ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan), ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan)); - ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; _bridge: %p; uniqueID: %s; linkedID:%s\n", + ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan), + ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan)); + ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; uniqueID: %s; linkedID:%s\n", ast_channel_masq(chan), ast_channel_masqr(chan), - ast_channel_internal_bridged_channel(chan), ast_channel_uniqueid(chan), ast_channel_linkedid(chan)); + ast_channel_uniqueid(chan), ast_channel_linkedid(chan)); if (ast_channel_masqr(chan)) { ast_log(LOG_NOTICE, "CHAN: masquerading as: %s; cdr: %p;\n", ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan))); } - if (ast_channel_internal_bridged_channel(chan)) { - ast_log(LOG_NOTICE, "CHAN: Bridged to %s\n", ast_channel_name(ast_channel_internal_bridged_channel(chan))); - } ast_log(LOG_NOTICE, "===== done ====\n"); } -/*! - * \brief return the first unlocked cdr in a possible chain - */ -static struct ast_cdr *pick_unlocked_cdr(struct ast_cdr *cdr) -{ - struct ast_cdr *cdr_orig = cdr; - while (cdr) { - if (!ast_test_flag(cdr,AST_CDR_FLAG_LOCKED)) - return cdr; - cdr = cdr->next; - } - return cdr_orig; /* everybody LOCKED or some other weirdness, like a NULL */ -} - static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features) { const char *feature; @@ -4249,17 +4059,14 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons for (feature = features; *feature; feature++) { struct ast_flags *party; - char this_feature; if (isupper(*feature)) { - party = &(config->features_caller); + party = &config->features_caller; } else { - party = &(config->features_callee); + party = &config->features_callee; } - this_feature = tolower(*feature); - - switch (this_feature) { + switch (tolower(*feature)) { case 't' : ast_set_flag(party, AST_FEATURE_REDIRECT); break; @@ -4277,6 +4084,7 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons break; default : ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature); + break; } } } @@ -4334,6 +4142,279 @@ void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval st /*! * \internal + * \brief Setup bridge builtin features. + * \since 12.0.0 + * + * \param features Bridge features to setup. + * \param chan Get features from this channel. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan) +{ + struct ast_flags *flags; + char dtmf[FEATURE_MAX_LEN]; + int res; + + ast_channel_lock(chan); + flags = ast_bridge_features_ds_get(chan); + ast_channel_unlock(chan); + if (!flags) { + return 0; + } + + res = 0; + ast_rdlock_call_features(); + if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) { + /* Add atxfer and blind transfer. */ + builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { +/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */ + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf, NULL, NULL, 1); + } + builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { +/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */ + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_DISCONNECT)) { + builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf)); + if (ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_PARKCALL)) { + builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_AUTOMON)) { + builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON)) { + builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf, NULL, NULL, 1); + } + } + ast_unlock_call_features(); + +#if 0 /* BUGBUG don't report errors untill all of the builtin features are supported. */ + return res ? -1 : 0; +#else + return 0; +#endif +} + +struct dtmf_hook_run_app { + /*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */ + unsigned int flags; + /*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */ + int moh_offset; + /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */ + int app_args_offset; + /*! Application name to run. */ + char app_name[0]; +}; + +/*! + * \internal + * \brief Setup bridge dynamic features. + * \since 12.0.0 + * + * \param bridge The bridge that the channel is part of + * \param bridge_channel Channel executing the feature + * \param hook_pvt Private data passed in when the hook was created + * + * \retval 0 Keep the callback hook. + * \retval -1 Remove the callback hook. + */ +static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + struct dtmf_hook_run_app *pvt = hook_pvt; + void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class); + + if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) { + run_it = ast_bridge_channel_write_app; + } else { + run_it = ast_bridge_channel_run_app; + } + +/* + * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run. + * + * This would replace DYNAMIC_PEERNAME which is redundant with + * BRIDGEPEER anyway. The value of DYNAMIC_WHO_TRIGGERED is + * really useful in the case of a multi-party bridge. + */ + run_it(bridge_channel, pvt->app_name, + pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL, + pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL); + return 0; +} + +/*! + * \internal + * \brief Add a dynamic DTMF feature hook to the bridge features. + * \since 12.0.0 + * + * \param features Bridge features to setup. + * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER). + * \param dtmf DTMF trigger sequence. + * \param app_name Dialplan application name to run. + * \param app_args Dialplan application arguments. (Empty or NULL if no arguments) + * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played) + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class) +{ + struct dtmf_hook_run_app *app_data; + size_t len_name = strlen(app_name) + 1; + size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1; + size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1; + size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh; + + /* Fill in application run hook data. */ + app_data = ast_malloc(len_data); + if (!app_data) { + return -1; + } + app_data->flags = flags; + app_data->app_args_offset = len_args ? len_name : 0; + app_data->moh_offset = len_moh ? len_name + len_args : 0; + strcpy(app_data->app_name, app_name);/* Safe */ + if (len_args) { + strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */ + } + if (len_moh) { + strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */ + } + + return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook, + app_data, ast_free_ptr, 1); +} + +/*! + * \internal + * \brief Setup bridge dynamic features. + * \since 12.0.0 + * + * \param features Bridge features to setup. + * \param chan Get features from this channel. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan) +{ + const char *feat; + char *dynamic_features = NULL; + char *tok; + int res; + + ast_channel_lock(chan); + feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"); + if (!ast_strlen_zero(feat)) { + dynamic_features = ast_strdupa(feat); + } + ast_channel_unlock(chan); + if (!dynamic_features) { + return 0; + } + +/* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (feature->sname) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */ + res = 0; + while ((tok = strsep(&dynamic_features, "#"))) { + struct feature_group *fg; + struct ast_call_feature *feature; + + AST_RWLIST_RDLOCK(&feature_groups); + fg = find_group(tok); + if (fg) { + struct feature_group_exten *fge; + + AST_LIST_TRAVERSE(&fg->features, fge, entry) { + res |= add_dynamic_dtmf_hook(features, fge->feature->flags, fge->exten, + fge->feature->app, fge->feature->app_args, fge->feature->moh_class); + } + } + AST_RWLIST_UNLOCK(&feature_groups); + + ast_rdlock_call_features(); + feature = find_dynamic_feature(tok); + if (feature) { + res |= add_dynamic_dtmf_hook(features, feature->flags, feature->exten, + feature->app, feature->app_args, feature->moh_class); + } + ast_unlock_call_features(); + } + return res; +} + +/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */ +/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */ +int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel) +{ + int res = 0; + + /* Always pass through any DTMF digits. */ + bridge_channel->features->dtmf_passthrough = 1; + + res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan); + res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan); + + return res; +} + +static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits) +{ + if (config->end_sound) { + ast_string_field_set(limits, duration_sound, config->end_sound); + } + + if (config->warning_sound) { + ast_string_field_set(limits, warning_sound, config->warning_sound); + } + + if (config->start_sound) { + ast_string_field_set(limits, connect_sound, config->start_sound); + } + + limits->frequency = config->warning_freq; + limits->warning = config->play_warning; +} + +/*! + * \internal brief Setup limit hook structures on calls that need limits + * + * \param config ast_bridge_config which provides the limit data + * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits + * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits + */ +static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits) +{ + if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) { + bridge_config_set_limits_warning_values(config, caller_limits); + } + + if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) { + bridge_config_set_limits_warning_values(config, callee_limits); + } + + caller_limits->duration = config->timelimit; + callee_limits->duration = config->timelimit; +} + +/*! + * \internal * \brief Check if Monitor needs to be started on a channel. * \since 12.0.0 * @@ -4375,6 +4456,24 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p } /*! + * \internal + * \brief Send the peer channel on its way on bridge start failure. + * \since 12.0.0 + * + * \param chan Chan to put into autoservice. + * \param peer Chan to send to after bridge goto or run hangup handlers and hangup. + * + * \return Nothing + */ +static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer) +{ + if (ast_after_bridge_goto_setup(peer) + || ast_pbx_start(peer)) { + ast_autoservice_chan_hangup_peer(chan, peer); + } +} + +/*! * \brief bridge the call and set CDR * * \param chan The bridge considers this channel the caller. @@ -4388,33 +4487,16 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p */ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config) { - /* Copy voice back and forth between the two channels. Give the peer - the ability to transfer calls with '#<extension' syntax. */ - struct ast_frame *f; - struct ast_channel *who; - char chan_featurecode[FEATURE_MAX_LEN + 1]=""; - char peer_featurecode[FEATURE_MAX_LEN + 1]=""; - char orig_channame[AST_CHANNEL_NAME]; - char orig_peername[AST_CHANNEL_NAME]; int res; - int diff; - int hasfeatures=0; - int hadfeatures=0; - int sendingdtmfdigit = 0; - int we_disabled_peer_cdr = 0; - struct ast_option_header *aoh; - struct ast_cdr *bridge_cdr = NULL; - struct ast_cdr *chan_cdr = ast_channel_cdr(chan); /* the proper chan cdr, if there are forked cdrs */ - struct ast_cdr *peer_cdr = ast_channel_cdr(peer); /* the proper chan cdr, if there are forked cdrs */ - struct ast_cdr *new_chan_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */ - struct ast_cdr *new_peer_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */ - struct ast_silence_generator *silgen = NULL; - /*! TRUE if h-exten or hangup handlers run. */ - int hangup_run = 0; + struct ast_bridge *bridge; + struct ast_bridge_features chan_features; + struct ast_bridge_features *peer_features; +/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */ pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer)); pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan)); +/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */ /* Clear any BLINDTRANSFER since the transfer has completed. */ pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL); pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL); @@ -4422,10 +4504,15 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES")); add_features_datastores(chan, peer, config); - /* This is an interesting case. One example is if a ringing channel gets redirected to - * an extension that picks up a parked call. This will make sure that the call taken - * out of parking gets told that the channel it just got bridged to is still ringing. */ - if (ast_channel_state(chan) == AST_STATE_RINGING && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) { + /* + * This is an interesting case. One example is if a ringing + * channel gets redirected to an extension that picks up a + * parked call. This will make sure that the call taken out of + * parking gets told that the channel it just got bridged to is + * still ringing. + */ + if (ast_channel_state(chan) == AST_STATE_RINGING + && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) { ast_indicate(peer, AST_CONTROL_RINGING); } @@ -4436,6 +4523,7 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a /* Answer if need be */ if (ast_channel_state(chan) != AST_STATE_UP) { if (ast_raw_answer(chan, 1)) { + bridge_failed_peer_goto(chan, peer); return -1; } } @@ -4446,571 +4534,123 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a ast_channel_log("Pre-bridge PEER Channel info", peer); #endif /* two channels are being marked as linked here */ - ast_channel_set_linkgroup(chan,peer); + ast_channel_set_linkgroup(chan, peer); - /* copy the userfield from the B-leg to A-leg if applicable */ - if (ast_channel_cdr(chan) && ast_channel_cdr(peer) && !ast_strlen_zero(ast_channel_cdr(peer)->userfield)) { - char tmp[256]; + /* + * If we are bridging a call, stop worrying about forwarding + * loops. We presume that if a call is being bridged, that the + * humans in charge know what they're doing. If they don't, + * well, what can we do about that? + */ + clear_dialed_interfaces(chan); + clear_dialed_interfaces(peer); - ast_channel_lock(chan); - if (!ast_strlen_zero(ast_channel_cdr(chan)->userfield)) { - snprintf(tmp, sizeof(tmp), "%s;%s", ast_channel_cdr(chan)->userfield, ast_channel_cdr(peer)->userfield); - ast_cdr_appenduserfield(chan, tmp); - } else { - ast_cdr_setuserfield(chan, ast_channel_cdr(peer)->userfield); - } - ast_channel_unlock(chan); - /* Don't delete the CDR; just disable it. */ - ast_set_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED); - we_disabled_peer_cdr = 1; - } - ast_copy_string(orig_channame,ast_channel_name(chan),sizeof(orig_channame)); - ast_copy_string(orig_peername,ast_channel_name(peer),sizeof(orig_peername)); - - if (!chan_cdr || (chan_cdr && !ast_test_flag(chan_cdr, AST_CDR_FLAG_POST_DISABLED))) { - ast_channel_lock_both(chan, peer); - if (chan_cdr) { - ast_set_flag(chan_cdr, AST_CDR_FLAG_MAIN); - ast_cdr_update(chan); - bridge_cdr = ast_cdr_dup_unique_swap(chan_cdr); - /* rip any forked CDR's off of the chan_cdr and attach - * them to the bridge_cdr instead */ - bridge_cdr->next = chan_cdr->next; - chan_cdr->next = NULL; - ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp)); - ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata)); - if (peer_cdr && !ast_strlen_zero(peer_cdr->userfield)) { - ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield)); - } - ast_cdr_setaccount(peer, ast_channel_accountcode(chan)); - } else { - /* better yet, in a xfer situation, find out why the chan cdr got zapped (pun unintentional) */ - bridge_cdr = ast_cdr_alloc(); /* this should be really, really rare/impossible? */ - ast_copy_string(bridge_cdr->channel, ast_channel_name(chan), sizeof(bridge_cdr->channel)); - ast_copy_string(bridge_cdr->dstchannel, ast_channel_name(peer), sizeof(bridge_cdr->dstchannel)); - ast_copy_string(bridge_cdr->uniqueid, ast_channel_uniqueid(chan), sizeof(bridge_cdr->uniqueid)); - ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp)); - ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata)); - ast_cdr_setcid(bridge_cdr, chan); - bridge_cdr->disposition = (ast_channel_state(chan) == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NULL; - bridge_cdr->amaflags = ast_channel_amaflags(chan) ? ast_channel_amaflags(chan) : ast_default_amaflags; - ast_copy_string(bridge_cdr->accountcode, ast_channel_accountcode(chan), sizeof(bridge_cdr->accountcode)); - /* Destination information */ - ast_copy_string(bridge_cdr->dst, ast_channel_exten(chan), sizeof(bridge_cdr->dst)); - ast_copy_string(bridge_cdr->dcontext, ast_channel_context(chan), sizeof(bridge_cdr->dcontext)); - if (peer_cdr) { - bridge_cdr->start = peer_cdr->start; - ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield)); - } else { - ast_cdr_start(bridge_cdr); - } - } - ast_channel_unlock(chan); - ast_channel_unlock(peer); + res = 0; + ast_channel_lock(chan); + res |= ast_bridge_features_ds_set(chan, &config->features_caller); + ast_channel_unlock(chan); + ast_channel_lock(peer); + res |= ast_bridge_features_ds_set(peer, &config->features_callee); + ast_channel_unlock(peer); + if (res) { + bridge_failed_peer_goto(chan, peer); + return -1; + } - ast_debug(4, "bridge answer set, chan answer set\n"); - /* peer_cdr->answer will be set when a macro runs on the peer; - in that case, the bridge answer will be delayed while the - macro plays on the peer channel. The peer answered the call - before the macro started playing. To the phone system, - this is billable time for the call, even tho the caller - hears nothing but ringing while the macro does its thing. */ - - /* Another case where the peer cdr's time will be set, is when - A self-parks by pickup up phone and dialing 700, then B - picks up A by dialing its parking slot; there may be more - practical paths that get the same result, tho... in which - case you get the previous answer time from the Park... which - is before the bridge's start time, so I added in the - tvcmp check to the if below */ - - if (peer_cdr && !ast_tvzero(peer_cdr->answer) && ast_tvcmp(peer_cdr->answer, bridge_cdr->start) >= 0) { - ast_cdr_setanswer(bridge_cdr, peer_cdr->answer); - ast_cdr_setdisposition(bridge_cdr, peer_cdr->disposition); - if (chan_cdr) { - ast_cdr_setanswer(chan_cdr, peer_cdr->answer); - ast_cdr_setdisposition(chan_cdr, peer_cdr->disposition); - } - } else { - ast_cdr_answer(bridge_cdr); - if (chan_cdr) { - ast_cdr_answer(chan_cdr); /* for the sake of cli status checks */ - } - } - if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT) && (chan_cdr || peer_cdr)) { - if (chan_cdr) { - ast_set_flag(chan_cdr, AST_CDR_FLAG_BRIDGED); - } - if (peer_cdr) { - ast_set_flag(peer_cdr, AST_CDR_FLAG_BRIDGED); - } - } - /* the DIALED flag may be set if a dialed channel is transfered - * and then bridged to another channel. In order for the - * bridge CDR to be written, the DIALED flag must not be - * present. */ - ast_clear_flag(bridge_cdr, AST_CDR_FLAG_DIALED); + /* Setup features. */ + res = ast_bridge_features_init(&chan_features); + peer_features = ast_bridge_features_new(); + if (res || !peer_features) { + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - ast_cel_report_event(chan, AST_CEL_BRIDGE_START, NULL, NULL, peer); - /* If we are bridging a call, stop worrying about forwarding loops. We presume that if - * a call is being bridged, that the humans in charge know what they're doing. If they - * don't, well, what can we do about that? */ - clear_dialed_interfaces(chan); - clear_dialed_interfaces(peer); + if (config->timelimit) { + struct ast_bridge_features_limits call_duration_limits_chan; + struct ast_bridge_features_limits call_duration_limits_peer; + int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */ - for (;;) { - struct ast_channel *other; /* used later */ + if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) { + ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n"); - res = ast_channel_bridge(chan, peer, config, &f, &who); + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; + } - if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) - || ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE)) { - /* Zombies are present time to leave! */ - res = -1; - if (f) { - ast_frfree(f); - } - goto before_you_go; - } - - /* When frame is not set, we are probably involved in a situation - where we've timed out. - When frame is set, we'll come this code twice; once for DTMF_BEGIN - and also for DTMF_END. If we flow into the following 'if' for both, then - our wait times are cut in half, as both will subtract from the - feature_timer. Not good! - */ - if (config->feature_timer && (!f || f->frametype == AST_FRAME_DTMF_END)) { - /* Update feature timer for next pass */ - diff = ast_tvdiff_ms(ast_tvnow(), config->feature_start_time); - if (res == AST_BRIDGE_RETRY) { - /* The feature fully timed out but has not been updated. Skip - * the potential round error from the diff calculation and - * explicitly set to expired. */ - config->feature_timer = -1; - } else { - config->feature_timer -= diff; - } + if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) { + ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n"); + ast_bridge_features_limits_destroy(&call_duration_limits_chan); - if (hasfeatures) { - if (config->feature_timer <= 0) { - /* Not *really* out of time, just out of time for - digits to come in for features. */ - ast_debug(1, "Timed out for feature!\n"); - if (!ast_strlen_zero(peer_featurecode)) { - ast_dtmf_stream(chan, peer, peer_featurecode, 0, f ? f->len : 0); - memset(peer_featurecode, 0, sizeof(peer_featurecode)); - } - if (!ast_strlen_zero(chan_featurecode)) { - ast_dtmf_stream(peer, chan, chan_featurecode, 0, f ? f->len : 0); - memset(chan_featurecode, 0, sizeof(chan_featurecode)); - } - if (f) - ast_frfree(f); - hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode); - if (!hasfeatures) { - /* No more digits expected - reset the timer */ - config->feature_timer = 0; - } - hadfeatures = hasfeatures; - /* Continue as we were */ - continue; - } else if (!f) { - /* The bridge returned without a frame and there is a feature in progress. - * However, we don't think the feature has quite yet timed out, so just - * go back into the bridge. */ - continue; - } - } else { - if (config->feature_timer <=0) { - /* We ran out of time */ - config->feature_timer = 0; - who = chan; - if (f) - ast_frfree(f); - f = NULL; - res = 0; - } - } - } - if (res < 0) { - if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer)) { - ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", ast_channel_name(chan), ast_channel_name(peer)); - } - goto before_you_go; + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - if (!f || (f->frametype == AST_FRAME_CONTROL && - (f->subclass.integer == AST_CONTROL_HANGUP || f->subclass.integer == AST_CONTROL_BUSY || - f->subclass.integer == AST_CONTROL_CONGESTION))) { - res = -1; - break; - } - /* many things should be sent to the 'other' channel */ - other = (who == chan) ? peer : chan; - if (f->frametype == AST_FRAME_CONTROL) { - switch (f->subclass.integer) { - case AST_CONTROL_RINGING: - case AST_CONTROL_FLASH: - case AST_CONTROL_MCID: - case -1: - ast_indicate(other, f->subclass.integer); - break; - case AST_CONTROL_CONNECTED_LINE: - if (ast_channel_connected_line_sub(who, other, f, 1) && - ast_channel_connected_line_macro(who, other, f, who != chan, 1)) { - ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen); - } - break; - case AST_CONTROL_REDIRECTING: - if (ast_channel_redirecting_sub(who, other, f, 1) && - ast_channel_redirecting_macro(who, other, f, who != chan, 1)) { - ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen); - } - break; - case AST_CONTROL_PVT_CAUSE_CODE: - case AST_CONTROL_AOC: - case AST_CONTROL_HOLD: - case AST_CONTROL_UNHOLD: - ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen); - break; - case AST_CONTROL_OPTION: - aoh = f->data.ptr; - /* Forward option Requests, but only ones we know are safe - * These are ONLY sent by chan_iax2 and I'm not convinced that - * they are useful. I haven't deleted them entirely because I - * just am not sure of the ramifications of removing them. */ - if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) { - switch (ntohs(aoh->option)) { - case AST_OPTION_TONE_VERIFY: - case AST_OPTION_TDD: - case AST_OPTION_RELAXDTMF: - case AST_OPTION_AUDIO_MODE: - case AST_OPTION_DIGIT_DETECT: - case AST_OPTION_FAX_DETECT: - ast_channel_setoption(other, ntohs(aoh->option), aoh->data, - f->datalen - sizeof(struct ast_option_header), 0); - } - } - break; - } - } else if (f->frametype == AST_FRAME_DTMF_BEGIN) { - struct ast_flags *cfg; - char dtmfcode[2] = { f->subclass.integer, }; - size_t featurelen; - - if (who == chan) { - featurelen = strlen(chan_featurecode); - cfg = &(config->features_caller); - } else { - featurelen = strlen(peer_featurecode); - cfg = &(config->features_callee); - } - /* Take a peek if this (possibly) matches a feature. If not, just pass this - * DTMF along untouched. If this is not the first digit of a multi-digit code - * then we need to fall through and stream the characters if it matches */ - if (featurelen == 0 - && feature_check(chan, cfg, &dtmfcode[0]) == AST_FEATURE_RETURN_PASSDIGITS) { - if (option_debug > 3) { - ast_log(LOG_DEBUG, "Passing DTMF through, since it is not a feature code\n"); - } - ast_write(other, f); - sendingdtmfdigit = 1; - } else { - /* If ast_opt_transmit_silence is set, then we need to make sure we are - * transmitting something while we hold on to the DTMF waiting for a - * feature. */ - if (!silgen && ast_opt_transmit_silence) { - silgen = ast_channel_start_silence_generator(other); - } - if (option_debug > 3) { - ast_log(LOG_DEBUG, "Not passing DTMF through, since it may be a feature code\n"); - } - } - } else if (f->frametype == AST_FRAME_DTMF_END) { - char *featurecode; - int sense; - unsigned int dtmfduration = f->len; - - hadfeatures = hasfeatures; - /* This cannot overrun because the longest feature is one shorter than our buffer */ - if (who == chan) { - sense = FEATURE_SENSE_CHAN; - featurecode = chan_featurecode; - } else { - sense = FEATURE_SENSE_PEER; - featurecode = peer_featurecode; - } + bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer); - if (sendingdtmfdigit == 1) { - /* We let the BEGIN go through happily, so let's not bother with the END, - * since we already know it's not something we bother with */ - ast_write(other, f); - sendingdtmfdigit = 0; - } else { - /*! append the event to featurecode. we rely on the string being zero-filled, and - * not overflowing it. - * \todo XXX how do we guarantee the latter ? - */ - featurecode[strlen(featurecode)] = f->subclass.integer; - /* Get rid of the frame before we start doing "stuff" with the channels */ - ast_frfree(f); - f = NULL; - if (silgen) { - ast_channel_stop_silence_generator(other, silgen); - silgen = NULL; - } - config->feature_timer = 0; - res = feature_interpret(chan, peer, config, featurecode, sense); - switch(res) { - case AST_FEATURE_RETURN_PASSDIGITS: - ast_dtmf_stream(other, who, featurecode, 0, dtmfduration); - /* Fall through */ - case AST_FEATURE_RETURN_SUCCESS: - memset(featurecode, 0, sizeof(chan_featurecode)); - break; - } - if (res >= AST_FEATURE_RETURN_PASSDIGITS) { - res = 0; - } else { - break; - } - hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode); - if (hadfeatures && !hasfeatures) { - /* Feature completed or timed out */ - config->feature_timer = 0; - } else if (hasfeatures) { - if (config->timelimit) { - /* No warning next time - we are waiting for feature code */ - ast_set_flag(config, AST_FEATURE_WARNING_ACTIVE); - } - config->feature_start_time = ast_tvnow(); - config->feature_timer = featuredigittimeout; - ast_debug(1, "Set feature timer to %ld ms\n", config->feature_timer); - } - } + if (ast_bridge_features_set_limits(&chan_features, &call_duration_limits_chan, 0)) { + abandon_call = 1; + } + if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) { + abandon_call = 1; } - if (f) - ast_frfree(f); - } - ast_cel_report_event(chan, AST_CEL_BRIDGE_END, NULL, NULL, peer); -before_you_go: - if (ast_channel_sending_dtmf_digit(chan)) { - ast_bridge_end_dtmf(chan, ast_channel_sending_dtmf_digit(chan), - ast_channel_sending_dtmf_tv(chan), "bridge end"); - } - if (ast_channel_sending_dtmf_digit(peer)) { - ast_bridge_end_dtmf(peer, ast_channel_sending_dtmf_digit(peer), - ast_channel_sending_dtmf_tv(peer), "bridge end"); - } + /* At this point we are done with the limits structs since they have been copied to the individual feature sets. */ + ast_bridge_features_limits_destroy(&call_duration_limits_chan); + ast_bridge_features_limits_destroy(&call_duration_limits_peer); - /* Just in case something weird happened and we didn't clean up the silence generator... */ - if (silgen) { - ast_channel_stop_silence_generator(who == chan ? peer : chan, silgen); - silgen = NULL; + if (abandon_call) { + ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n"); + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; + } } - /* Wait for any dual redirect to complete. */ - while (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) { - sched_yield(); + /* Create bridge */ + bridge = ast_bridge_basic_new(); + if (!bridge) { + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT)) { - ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); /* its job is done */ - if (bridge_cdr) { - ast_cdr_discard(bridge_cdr); - /* QUESTION: should we copy bridge_cdr fields to the peer before we throw it away? */ - } - return res; /* if we shouldn't do the h-exten, we shouldn't do the bridge cdr, either! */ + /* Put peer into the bridge */ + if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) { + ast_bridge_destroy(bridge); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - if (config->end_bridge_callback) { - config->end_bridge_callback(config->end_bridge_callback_data); - } + /* Join bridge */ + ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1); - /* run the hangup exten on the chan object IFF it was NOT involved in a parking situation - * if it were, then chan belongs to a different thread now, and might have been hung up long - * ago. + /* + * 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 really only happen with + * AST_SOFTHANGUP_ASYNCGOTO. */ - if (!(ast_channel_softhangup_internal_flag(chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE)) - && !ast_test_flag(&config->features_caller, AST_FEATURE_NO_H_EXTEN)) { - struct ast_cdr *swapper = NULL; - char savelastapp[AST_MAX_EXTENSION]; - char savelastdata[AST_MAX_EXTENSION]; - char save_context[AST_MAX_CONTEXT]; - char save_exten[AST_MAX_EXTENSION]; - int save_prio; - - ast_channel_lock(chan); - if (bridge_cdr) { - /* - * Swap the bridge_cdr and the chan cdr for a moment, and let - * the hangup dialplan code operate on it. - */ - swapper = ast_channel_cdr(chan); - ast_channel_cdr_set(chan, bridge_cdr); - - /* protect the lastapp/lastdata against the effects of the hangup/dialplan code */ - ast_copy_string(savelastapp, bridge_cdr->lastapp, sizeof(bridge_cdr->lastapp)); - ast_copy_string(savelastdata, bridge_cdr->lastdata, sizeof(bridge_cdr->lastdata)); - } - ast_copy_string(save_context, ast_channel_context(chan), sizeof(save_context)); - ast_copy_string(save_exten, ast_channel_exten(chan), sizeof(save_exten)); - save_prio = ast_channel_priority(chan); - ast_channel_unlock(chan); - - ast_autoservice_start(peer); - if (ast_exists_extension(chan, ast_channel_context(chan), "h", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, - ast_channel_caller(chan)->id.number.str, NULL))) { - ast_pbx_h_exten_run(chan, ast_channel_context(chan)); - hangup_run = 1; - } else if (!ast_strlen_zero(ast_channel_macrocontext(chan)) - && ast_exists_extension(chan, ast_channel_macrocontext(chan), "h", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, - ast_channel_caller(chan)->id.number.str, NULL))) { - ast_pbx_h_exten_run(chan, ast_channel_macrocontext(chan)); - hangup_run = 1; - } - if (ast_pbx_hangup_handler_run(chan)) { - /* Indicate hangup handlers were run. */ - hangup_run = 1; - } - ast_autoservice_stop(peer); - - ast_channel_lock(chan); - - /* swap it back */ - ast_channel_context_set(chan, save_context); - ast_channel_exten_set(chan, save_exten); - ast_channel_priority_set(chan, save_prio); - if (bridge_cdr) { - if (ast_channel_cdr(chan) == bridge_cdr) { - ast_channel_cdr_set(chan, swapper); - - /* Restore the lastapp/lastdata */ - ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp)); - ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata)); - } else { - bridge_cdr = NULL; - } - } - ast_channel_unlock(chan); + res = -1; + ast_channel_lock(chan); + if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) { + res = 0; } + ast_channel_unlock(chan); - /* obey the NoCDR() wishes. -- move the DISABLED flag to the bridge CDR if it was set on the channel during the bridge... */ - new_chan_cdr = pick_unlocked_cdr(ast_channel_cdr(chan)); /* the proper chan cdr, if there are forked cdrs */ + ast_bridge_features_cleanup(&chan_features); - /* - * If the channel CDR has been modified during the call, record - * the changes in the bridge cdr, BUT, if hangup_run, the CDR - * got swapped so don't overwrite what was done in the - * h-extension or hangup handlers. What a mess. This is why - * you never touch CDR code. - */ - if (new_chan_cdr && bridge_cdr && !hangup_run) { - ast_cdr_copy_vars(bridge_cdr, new_chan_cdr); - ast_copy_string(bridge_cdr->userfield, new_chan_cdr->userfield, sizeof(bridge_cdr->userfield)); - bridge_cdr->amaflags = new_chan_cdr->amaflags; - ast_copy_string(bridge_cdr->accountcode, new_chan_cdr->accountcode, sizeof(bridge_cdr->accountcode)); - if (ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED)) { - ast_set_flag(bridge_cdr, AST_CDR_FLAG_POST_DISABLED); - } - } - - /* we can post the bridge CDR at this point */ - if (bridge_cdr) { - ast_cdr_end(bridge_cdr); - ast_cdr_detach(bridge_cdr); - } - - /* do a specialized reset on the beginning channel - CDR's, if they still exist, so as not to mess up - issues in future bridges; - - Here are the rules of the game: - 1. The chan and peer channel pointers will not change - during the life of the bridge. - 2. But, in transfers, the channel names will change. - between the time the bridge is started, and the - time the channel ends. - Usually, when a channel changes names, it will - also change CDR pointers. - 3. Usually, only one of the two channels (chan or peer) - will change names. - 4. Usually, if a channel changes names during a bridge, - it is because of a transfer. Usually, in these situations, - it is normal to see 2 bridges running simultaneously, and - it is not unusual to see the two channels that change - swapped between bridges. - 5. After a bridge occurs, we have 2 or 3 channels' CDRs - to attend to; if the chan or peer changed names, - we have the before and after attached CDR's. - */ - - if (new_chan_cdr) { - struct ast_channel *chan_ptr = NULL; - - if (strcasecmp(orig_channame, ast_channel_name(chan)) != 0) { - /* old channel */ - if ((chan_ptr = ast_channel_get_by_name(orig_channame))) { - ast_channel_lock(chan_ptr); - if (!ast_bridged_channel(chan_ptr)) { - struct ast_cdr *cur; - for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) { - if (cur == chan_cdr) { - break; - } - } - if (cur) { - ast_cdr_specialized_reset(chan_cdr, 0); - } - } - ast_channel_unlock(chan_ptr); - chan_ptr = ast_channel_unref(chan_ptr); - } - /* new channel */ - ast_cdr_specialized_reset(new_chan_cdr, 0); - } else { - ast_cdr_specialized_reset(ast_channel_cdr(chan), 0); /* nothing changed, reset the chan cdr */ - } - } - - { - struct ast_channel *chan_ptr = NULL; - new_peer_cdr = pick_unlocked_cdr(ast_channel_cdr(peer)); /* the proper chan cdr, if there are forked cdrs */ - if (new_chan_cdr && ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED) && new_peer_cdr && !ast_test_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED)) - ast_set_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED); /* DISABLED is viral-- it will propagate across a bridge */ - if (strcasecmp(orig_peername, ast_channel_name(peer)) != 0) { - /* old channel */ - if ((chan_ptr = ast_channel_get_by_name(orig_peername))) { - ast_channel_lock(chan_ptr); - if (!ast_bridged_channel(chan_ptr)) { - struct ast_cdr *cur; - for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) { - if (cur == peer_cdr) { - break; - } - } - if (cur) { - ast_cdr_specialized_reset(peer_cdr, 0); - } - } - ast_channel_unlock(chan_ptr); - chan_ptr = ast_channel_unref(chan_ptr); - } - /* new channel */ - if (new_peer_cdr) { - ast_cdr_specialized_reset(new_peer_cdr, 0); - } - } else { - if (we_disabled_peer_cdr) { - ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED); - } - ast_cdr_specialized_reset(ast_channel_cdr(peer), 0); /* nothing changed, reset the peer cdr */ - } +/* BUGBUG this is used by Dial and FollowMe for CDR information. By Queue for Queue stats like CDRs. */ + if (res && config->end_bridge_callback) { + config->end_bridge_callback(config->end_bridge_callback_data); } return res; @@ -5456,395 +5096,6 @@ AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS AST_APP_OPTION('s', AST_PARK_OPT_SILENCE), END_OPTIONS ); -/*! \brief Park a call */ -static int park_call_exec(struct ast_channel *chan, const char *data) -{ - struct ast_park_call_args args = { 0, }; - struct ast_flags flags = { 0 }; - char orig_exten[AST_MAX_EXTENSION]; - int orig_priority; - int res; - const char *pl_name; - char *parse; - struct park_app_args app_args; - - /* - * Cache the original channel name because we are going to - * masquerade the channel. Prefer the BLINDTRANSFER channel - * name over this channel name. BLINDTRANSFER could be set if - * the parking access extension did not get detected and we are - * executing the Park application from the dialplan. - * - * The orig_chan_name is used to return the call to the - * originator on parking timeout. - */ - args.orig_chan_name = ast_strdupa(S_OR( - pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"), ast_channel_name(chan))); - - /* Answer if call is not up */ - if (ast_channel_state(chan) != AST_STATE_UP) { - if (ast_answer(chan)) { - return -1; - } - - /* Sleep to allow VoIP streams to settle down */ - if (ast_safe_sleep(chan, 1000)) { - return -1; - } - } - - /* Process the dialplan application options. */ - parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(app_args, parse); - - if (!ast_strlen_zero(app_args.timeout)) { - if (sscanf(app_args.timeout, "%30d", &args.timeout) != 1) { - ast_log(LOG_WARNING, "Invalid timeout '%s' provided\n", app_args.timeout); - args.timeout = 0; - } - } - if (!ast_strlen_zero(app_args.return_con)) { - args.return_con = app_args.return_con; - } - if (!ast_strlen_zero(app_args.return_ext)) { - args.return_ext = app_args.return_ext; - } - if (!ast_strlen_zero(app_args.return_pri)) { - if (sscanf(app_args.return_pri, "%30d", &args.return_pri) != 1) { - ast_log(LOG_WARNING, "Invalid priority '%s' specified\n", app_args.return_pri); - args.return_pri = 0; - } - } - - ast_app_parse_options(park_call_options, &flags, NULL, app_args.options); - args.flags = flags.flags; - - /* - * Setup the exten/priority to be s/1 since we don't know where - * this call should return. - */ - ast_copy_string(orig_exten, ast_channel_exten(chan), sizeof(orig_exten)); - orig_priority = ast_channel_priority(chan); - ast_channel_exten_set(chan, "s"); - ast_channel_priority_set(chan, 1); - - /* Park the call */ - if (!ast_strlen_zero(app_args.pl_name)) { - pl_name = app_args.pl_name; - } else { - pl_name = findparkinglotname(chan); - } - if (ast_strlen_zero(pl_name)) { - /* Parking lot is not specified, so use the default parking lot. */ - args.parkinglot = parkinglot_addref(default_parkinglot); - } else { - args.parkinglot = find_parkinglot(pl_name); - if (!args.parkinglot && parkeddynamic) { - args.parkinglot = create_dynamic_parkinglot(pl_name, chan); - } - } - if (args.parkinglot) { - res = masq_park_call(chan, chan, &args); - parkinglot_unref(args.parkinglot); - } else { - /* Parking failed because the parking lot does not exist. */ - if (!ast_test_flag(&args, AST_PARK_OPT_SILENCE)) { - ast_stream_and_wait(chan, "pbx-parkingfailed", ""); - } - res = -1; - } - if (res) { - /* Park failed, try to continue in the dialplan. */ - ast_channel_exten_set(chan, orig_exten); - ast_channel_priority_set(chan, orig_priority); - res = 0; - } else { - /* Park succeeded. */ - res = -1; - } - - return res; -} - -/*! \brief Pickup parked call */ -static int parked_call_exec(struct ast_channel *chan, const char *data) -{ - int res; - struct ast_channel *peer = NULL; - struct parkeduser *pu; - struct ast_context *con; - char *parse; - const char *pl_name; - int park = 0; - struct ast_bridge_config config; - struct ast_parkinglot *parkinglot; - AST_DECLARE_APP_ARGS(app_args, - AST_APP_ARG(pl_space); /*!< Parking lot space to retrieve if present. */ - AST_APP_ARG(pl_name); /*!< Parking lot name to use if present. */ - AST_APP_ARG(dummy); /*!< Place to put any remaining args string. */ - ); - - parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(app_args, parse); - - if (!ast_strlen_zero(app_args.pl_space)) { - if (sscanf(app_args.pl_space, "%30u", &park) != 1) { - ast_log(LOG_WARNING, "Specified parking extension not a number: %s\n", - app_args.pl_space); - park = -1; - } - } - - if (!ast_strlen_zero(app_args.pl_name)) { - pl_name = app_args.pl_name; - } else { - pl_name = findparkinglotname(chan); - } - if (ast_strlen_zero(pl_name)) { - /* Parking lot is not specified, so use the default parking lot. */ - parkinglot = parkinglot_addref(default_parkinglot); - } else { - parkinglot = find_parkinglot(pl_name); - if (!parkinglot) { - /* It helps to answer the channel if not already up. :) */ - if (ast_channel_state(chan) != AST_STATE_UP) { - ast_answer(chan); - } - if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) { - ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", - "pbx-invalidpark", ast_channel_name(chan)); - } - ast_log(LOG_WARNING, - "Channel %s tried to retrieve parked call from unknown parking lot '%s'\n", - ast_channel_name(chan), pl_name); - return -1; - } - } - - AST_LIST_LOCK(&parkinglot->parkings); - AST_LIST_TRAVERSE_SAFE_BEGIN(&parkinglot->parkings, pu, list) { - if ((ast_strlen_zero(app_args.pl_space) || pu->parkingnum == park) - && !pu->notquiteyet && !ast_channel_pbx(pu->chan)) { - /* The parking space has a call and can be picked up now. */ - AST_LIST_REMOVE_CURRENT(list); - break; - } - } - AST_LIST_TRAVERSE_SAFE_END; - if (pu) { - struct ast_callid *callid = ast_read_threadstorage_callid(); - - /* Found a parked call to pickup. */ - peer = pu->chan; - - /* We need to map the call id we have from this thread to the channel we found. */ - if (callid) { - ast_channel_callid_set(peer, callid); - callid = ast_callid_unref(callid); - } - - con = ast_context_find(parkinglot->cfg.parking_con); - if (con) { - if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) { - ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); - } else { - notify_metermaids(pu->parkingexten, parkinglot->cfg.parking_con, AST_DEVICE_NOT_INUSE); - } - } else { - ast_log(LOG_WARNING, "Whoa, no parking context?\n"); - } - - ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "UnParkedCall", chan); - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a call has been unparked.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Exten'])" /> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Parkinglot'])" /> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='From'])" /> - </syntax> - <see-also> - <ref type="application">ParkedCall</ref> - <ref type="managerEvent">ParkedCall</ref> - </see-also> - </managerEventInstance> - ***/ - ast_manager_event(pu->chan, EVENT_FLAG_CALL, "UnParkedCall", - "Exten: %s\r\n" - "Channel: %s\r\n" - "Parkinglot: %s\r\n" - "From: %s\r\n" - "CallerIDNum: %s\r\n" - "CallerIDName: %s\r\n" - "ConnectedLineNum: %s\r\n" - "ConnectedLineName: %s\r\n" - "Uniqueid: %s\r\n", - pu->parkingexten, ast_channel_name(pu->chan), pu->parkinglot->name, - ast_channel_name(chan), - S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"), - S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"), - ast_channel_uniqueid(pu->chan) - ); - - /* Stop entertaining the caller. */ - switch (pu->hold_method) { - case AST_CONTROL_HOLD: - ast_indicate(pu->chan, AST_CONTROL_UNHOLD); - break; - case AST_CONTROL_RINGING: - ast_indicate(pu->chan, -1); - break; - default: - break; - } - pu->hold_method = 0; - - parkinglot_unref(pu->parkinglot); - ast_free(pu); - } - AST_LIST_UNLOCK(&parkinglot->parkings); - - if (peer) { - /* Update connected line between retrieving call and parked call. */ - struct ast_party_connected_line connected; - - ast_party_connected_line_init(&connected); - - /* Send our caller-id to peer. */ - ast_channel_lock(chan); - ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan)); - ast_channel_unlock(chan); - connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; - if (ast_channel_connected_line_sub(chan, peer, &connected, 0) && - ast_channel_connected_line_macro(chan, peer, &connected, 0, 0)) { - ast_channel_update_connected_line(peer, &connected, NULL); - } - - /* - * Get caller-id from peer. - * - * Update the retrieving call before it is answered if possible - * for best results. Some phones do not support updating the - * connected line information after connection. - */ - ast_channel_lock(peer); - ast_connected_line_copy_from_caller(&connected, ast_channel_caller(peer)); - ast_channel_unlock(peer); - connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; - if (ast_channel_connected_line_sub(peer, chan, &connected, 0) && - ast_channel_connected_line_macro(peer, chan, &connected, 1, 0)) { - ast_channel_update_connected_line(chan, &connected, NULL); - } - - ast_party_connected_line_free(&connected); - } - - /* JK02: it helps to answer the channel if not already up */ - if (ast_channel_state(chan) != AST_STATE_UP) { - ast_answer(chan); - } - - if (peer) { - struct ast_datastore *features_datastore; - struct ast_dial_features *dialfeatures; - - /* Play a courtesy to the source(s) configured to prefix the bridge connecting */ - if (!ast_strlen_zero(courtesytone)) { - static const char msg[] = "courtesy tone"; - - switch (parkedplay) { - case 0:/* Courtesy tone to pickup chan */ - res = play_message_to_chans(chan, peer, -1, msg, courtesytone); - break; - case 1:/* Courtesy tone to parked chan */ - res = play_message_to_chans(chan, peer, 1, msg, courtesytone); - break; - case 2:/* Courtesy tone to both chans */ - res = play_message_to_chans(chan, peer, 0, msg, courtesytone); - break; - default: - res = 0; - break; - } - if (res) { - ast_autoservice_chan_hangup_peer(chan, peer); - parkinglot_unref(parkinglot); - return -1; - } - } - - res = ast_channel_make_compatible(chan, peer); - if (res < 0) { - ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", ast_channel_name(chan), ast_channel_name(peer)); - ast_autoservice_chan_hangup_peer(chan, peer); - parkinglot_unref(parkinglot); - return -1; - } - /* This runs sorta backwards, since we give the incoming channel control, as if it - were the person called. */ - ast_verb(3, "Channel %s connected to parked call %d\n", ast_channel_name(chan), park); - - pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer)); - ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer)); - memset(&config, 0, sizeof(struct ast_bridge_config)); - - /* Get datastore for peer and apply it's features to the callee side of the bridge config */ - ast_channel_lock(peer); - features_datastore = ast_channel_datastore_find(peer, &dial_features_info, NULL); - if (features_datastore && (dialfeatures = features_datastore->data)) { - ast_copy_flags(&config.features_callee, &dialfeatures->my_features, - AST_FLAGS_ALL); - } - ast_channel_unlock(peer); - - if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); - } - if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); - } - if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL); - } - if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL); - } - if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT); - } - if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT); - } - if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON); - } - if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON); - } - - res = ast_bridge_call(chan, peer, &config); - - pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer)); - ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer)); - - /* Simulate the PBX hanging up */ - ast_autoservice_chan_hangup_peer(chan, peer); - } else { - if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) { - ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", - ast_channel_name(chan)); - } - ast_verb(3, "Channel %s tried to retrieve nonexistent parked call %d\n", - ast_channel_name(chan), park); - res = -1; - } - - parkinglot_unref(parkinglot); - return res; -} - /*! * \brief Unreference parkinglot object. */ @@ -6282,6 +5533,10 @@ static void process_applicationmap_line(struct ast_variable *var) return; } + /* + * We will parse and require correct syntax for the ActivatedBy + * option, but the ActivatedBy option is not honored anymore. + */ if (ast_strlen_zero(args.activatedby)) { ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); } else if (!strcasecmp(args.activatedby, "caller")) { @@ -7235,8 +6490,6 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl { int i; struct ast_call_feature *feature; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; #define HFS_FORMAT "%-25s %-7s %-7s\n" switch (cmd) { @@ -7292,28 +6545,7 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl } AST_RWLIST_UNLOCK(&feature_groups); - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - ast_cli(a->fd, "\nCall parking (Parking lot: %s)\n", curlot->name); - ast_cli(a->fd, "------------\n"); - ast_cli(a->fd,"%-22s: %s\n", "Parking extension", curlot->cfg.parkext); - ast_cli(a->fd,"%-22s: %s\n", "Parking context", curlot->cfg.parking_con); - ast_cli(a->fd,"%-22s: %d-%d\n", "Parked call extensions", - curlot->cfg.parking_start, curlot->cfg.parking_stop); - ast_cli(a->fd,"%-22s: %u ms\n", "Parkingtime", curlot->cfg.parkingtime); - ast_cli(a->fd,"%-22s: %s\n", "Comeback to origin", - (curlot->cfg.comebacktoorigin ? "yes" : "no")); - ast_cli(a->fd,"%-22s: %s%s\n", "Comeback context", - curlot->cfg.comebackcontext, (curlot->cfg.comebacktoorigin ? - " (comebacktoorigin=yes, not used)" : "")); - ast_cli(a->fd,"%-22s: %d\n", "Comeback dial time", - curlot->cfg.comebackdialtime); - ast_cli(a->fd,"%-22s: %s\n", "MusicOnHold class", curlot->cfg.mohclass); - ast_cli(a->fd,"%-22s: %s\n", "Enabled", AST_CLI_YESNO(!curlot->disabled)); - ast_cli(a->fd,"\n"); - ao2_ref(curlot, -1); - } - ao2_iterator_destroy(&iter); + ast_cli(a->fd, "\n"); return CLI_SUCCESS; } @@ -7511,8 +6743,8 @@ static int action_bridge(struct mansession *s, const struct message *m) return 0; } - tobj->chan = tmpchana; - tobj->peer = tmpchanb; + tobj->chan = tmpchanb; + tobj->peer = tmpchana; tobj->return_to_pbx = 1; if (ast_true(playtone)) { @@ -7545,6 +6777,7 @@ static int action_bridge(struct mansession *s, const struct message *m) "Channel1: %s\r\n" "Channel2: %s\r\n", ast_channel_name(tmpchana), ast_channel_name(tmpchanb)); +/* BUGBUG there seems to be no COLP update here. */ bridge_call_thread_launch(tobj); astman_send_ack(s, m, "Launched bridge thread with success"); @@ -7552,180 +6785,11 @@ static int action_bridge(struct mansession *s, const struct message *m) return 0; } -/*! - * \brief CLI command to list parked calls - * \param e - * \param cmd - * \param a - * - * Check right usage, lock parking lot, display parked calls, unlock parking lot list. - * \retval CLI_SUCCESS on success. - * \retval CLI_SHOWUSAGE on incorrect number of arguments. - * \retval NULL when tab completion is used. - */ -static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct parkeduser *cur; - int numparked = 0; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; - - switch (cmd) { - case CLI_INIT: - e->command = "parkedcalls show"; - e->usage = - "Usage: parkedcalls show\n" - " List currently parked calls\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (a->argc > e->args) - return CLI_SHOWUSAGE; - - ast_cli(a->fd, "%-10s %-25s (%-15s %-12s %4s) %s\n", "Num", "Channel", - "Context", "Extension", "Pri", "Timeout"); - - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - int lotparked = 0; - - /* subtract ref for iterator and for configured parking lot */ - ast_cli(a->fd, "*** Parking lot: %s (%d)\n", curlot->name, - ao2_ref(curlot, 0) - 2 - (curlot == default_parkinglot)); - - AST_LIST_LOCK(&curlot->parkings); - AST_LIST_TRAVERSE(&curlot->parkings, cur, list) { - ast_cli(a->fd, "%-10.10s %-25s (%-15s %-12s %4d) %6lds\n", - cur->parkingexten, ast_channel_name(cur->chan), cur->context, cur->exten, - cur->priority, - (long) (cur->start.tv_sec + (cur->parkingtime / 1000) - time(NULL))); - ++lotparked; - } - AST_LIST_UNLOCK(&curlot->parkings); - if (lotparked) { - numparked += lotparked; - ast_cli(a->fd, " %d parked call%s in parking lot %s\n", lotparked, - ESS(lotparked), curlot->name); - } - - ao2_ref(curlot, -1); - } - ao2_iterator_destroy(&iter); - - ast_cli(a->fd, "---\n%d parked call%s in total.\n", numparked, ESS(numparked)); - - return CLI_SUCCESS; -} - static struct ast_cli_entry cli_features[] = { AST_CLI_DEFINE(handle_feature_show, "Lists configured features"), AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"), - AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"), }; -static int manager_parkinglot_list(struct mansession *s, const struct message *m) -{ - const char *id = astman_get_header(m, "ActionID"); - char idText[256] = ""; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; - - if (!ast_strlen_zero(id)) - snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); - - astman_send_ack(s, m, "Parking lots will follow"); - - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - astman_append(s, "Event: Parkinglot\r\n" - "Name: %s\r\n" - "StartExten: %d\r\n" - "StopExten: %d\r\n" - "Timeout: %d\r\n" - "\r\n", - curlot->name, - curlot->cfg.parking_start, - curlot->cfg.parking_stop, - curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime); - ao2_ref(curlot, -1); - } - - astman_append(s, - "Event: ParkinglotsComplete\r\n" - "%s" - "\r\n",idText); - - return RESULT_SUCCESS; -} - -/*! - * \brief Dump parking lot status - * \param s - * \param m - * - * Lock parking lot, iterate list and append parked calls status, unlock parking lot. - * \return Always RESULT_SUCCESS - */ -static int manager_parking_status(struct mansession *s, const struct message *m) -{ - struct parkeduser *cur; - const char *id = astman_get_header(m, "ActionID"); - char idText[256] = ""; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; - int numparked = 0; - long now = time(NULL); - - if (!ast_strlen_zero(id)) - snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); - - astman_send_ack(s, m, "Parked calls will follow"); - - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - AST_LIST_LOCK(&curlot->parkings); - AST_LIST_TRAVERSE(&curlot->parkings, cur, list) { - astman_append(s, "Event: ParkedCall\r\n" - "Parkinglot: %s\r\n" - "Exten: %d\r\n" - "Channel: %s\r\n" - "From: %s\r\n" - "Timeout: %ld\r\n" - "Duration: %ld\r\n" - "CallerIDNum: %s\r\n" - "CallerIDName: %s\r\n" - "ConnectedLineNum: %s\r\n" - "ConnectedLineName: %s\r\n" - "%s" - "\r\n", - curlot->name, - cur->parkingnum, ast_channel_name(cur->chan), cur->peername, - (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now, - now - (long) cur->start.tv_sec, - S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */ - S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""), - S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */ - S_COR(ast_channel_connected(cur->chan)->id.name.valid, ast_channel_connected(cur->chan)->id.name.str, ""), - idText); - ++numparked; - } - AST_LIST_UNLOCK(&curlot->parkings); - ao2_ref(curlot, -1); - } - ao2_iterator_destroy(&iter); - - astman_append(s, - "Event: ParkedCallsComplete\r\n" - "Total: %d\r\n" - "%s" - "\r\n", - numparked, idText); - - return RESULT_SUCCESS; -} - /*! * \brief Create manager event for parked calls * \param s @@ -8210,8 +7274,11 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con calldurationlimit->tv_usec = (config->timelimit % 1000) * 1000; ast_verb(3, "Setting call duration limit to %.3lf seconds.\n", calldurationlimit->tv_sec + calldurationlimit->tv_usec / 1000000.0); - config->timelimit = play_to_caller = play_to_callee = - config->play_warning = config->warning_freq = 0; + play_to_caller = 0; + play_to_callee = 0; + config->timelimit = 0; + config->play_warning = 0; + config->warning_freq = 0; } else { ast_verb(4, "Limit Data for this call:\n"); ast_verb(4, "timelimit = %ld ms (%.3lf s)\n", config->timelimit, config->timelimit / 1000.0); @@ -8242,12 +7309,17 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con */ static int bridge_exec(struct ast_channel *chan, const char *data) { - struct ast_channel *current_dest_chan, *final_dest_chan, *chans[2]; + struct ast_channel *current_dest_chan; + struct ast_channel *final_dest_chan; + struct ast_channel *chans[2]; char *tmp_data = NULL; struct ast_flags opts = { 0, }; struct ast_bridge_config bconfig = { { 0, }, }; char *opt_args[OPT_ARG_ARRAY_SIZE]; struct timeval calldurationlimit = { 0, }; + const char *context; + const char *extension; + int priority; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(dest_chan); @@ -8357,8 +7429,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data) "Channel1: %s\r\n" "Channel2: %s\r\n", ast_channel_name(chan), ast_channel_name(final_dest_chan)); - /* Maybe we should return this channel to the PBX? */ - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); + bridge_failed_peer_goto(chan, final_dest_chan); pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE"); current_dest_chan = ast_channel_unref(current_dest_chan); @@ -8406,60 +7477,28 @@ static int bridge_exec(struct ast_channel *chan, const char *data) if (ast_test_flag(&opts, OPT_CALLER_PARK)) ast_set_flag(&(bconfig.features_caller), AST_FEATURE_PARKCALL); - /* - * Don't let the after-bridge code run the h-exten. We want to - * continue in the dialplan. - */ - ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); + /* Setup after bridge goto location. */ + if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) { + ast_channel_lock(chan); + context = ast_strdupa(ast_channel_context(chan)); + extension = ast_strdupa(ast_channel_exten(chan)); + priority = ast_channel_priority(chan); + ast_channel_unlock(chan); + ast_after_bridge_set_go_on(final_dest_chan, context, extension, priority, + opt_args[OPT_ARG_CALLEE_GO_ON]); + } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) { + ast_channel_lock(final_dest_chan); + context = ast_strdupa(ast_channel_context(final_dest_chan)); + extension = ast_strdupa(ast_channel_exten(final_dest_chan)); + priority = ast_channel_priority(final_dest_chan); + ast_channel_unlock(final_dest_chan); + ast_after_bridge_set_goto(final_dest_chan, context, extension, priority); + } + ast_bridge_call(chan, final_dest_chan, &bconfig); /* The bridge has ended, set BRIDGERESULT to SUCCESS. */ pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS"); - - /* If the other channel has not been hung up, return it to the PBX */ - if (!ast_check_hangup(final_dest_chan)) { - if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) { - char *caller_context; - char *caller_extension; - int caller_priority; - int goto_opt; - - ast_channel_lock(chan); - caller_context = ast_strdupa(ast_channel_context(chan)); - caller_extension = ast_strdupa(ast_channel_exten(chan)); - caller_priority = ast_channel_priority(chan); - ast_channel_unlock(chan); - - if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) { - ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]); - /* Set current dialplan position to bridger dialplan position */ - goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority) - /* Then perform the goto */ - || ast_parseable_goto(final_dest_chan, opt_args[OPT_ARG_CALLEE_GO_ON]); - } else { /* F() */ - goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority + 1); - } - if (goto_opt || ast_pbx_start(final_dest_chan)) { - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } - } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) { - ast_debug(1, "starting new PBX in %s,%s,%d for chan %s\n", - ast_channel_context(final_dest_chan), ast_channel_exten(final_dest_chan), - ast_channel_priority(final_dest_chan), ast_channel_name(final_dest_chan)); - - if (ast_pbx_start(final_dest_chan)) { - ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", ast_channel_name(final_dest_chan)); - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } else { - ast_debug(1, "SUCCESS continuing PBX on chan %s\n", ast_channel_name(final_dest_chan)); - } - } else { - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } - } else { - ast_debug(1, "chan %s was hungup\n", ast_channel_name(final_dest_chan)); - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } done: ast_free((char *) bconfig.warning_sound); ast_free((char *) bconfig.end_sound); @@ -9130,10 +8169,7 @@ static void features_shutdown(void) ast_custom_function_unregister(&feature_function); ast_manager_unregister("Bridge"); ast_manager_unregister("Park"); - ast_manager_unregister("Parkinglots"); - ast_manager_unregister("ParkedCalls"); - ast_unregister_application(parkcall); - ast_unregister_application(parkedcall); + ast_unregister_application(app_bridge); pthread_cancel(parking_thread); @@ -9161,12 +8197,7 @@ int ast_features_init(void) return -1; } ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL); - res = ast_register_application2(parkedcall, parked_call_exec, NULL, NULL, NULL); - if (!res) - res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL); if (!res) { - ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status); - ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list); ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park); ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge); } @@ -9182,4 +8213,3 @@ int ast_features_init(void) return res; } - |